线程杂谈2 -续

本文介绍了临界区(CriticalSection)作为线程同步机制的工作原理,并通过实例演示了如何使用临界区保护共享资源,避免数据竞争。同时,文章还讨论了在多线程环境中可能发生的死锁现象及其成因。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

同步机制

当有多个线程同时读写一个资源时,使用同步机制可以保护数据的完整性,也可以防止数据被损坏。线程同步有很多种方式,但最常用的还是临界区(Critical Sections),这里只讨论临界区。不过之前,先说明一下同步(Synchronous)和异步(Asynchronous)的概念。当程序1调用程序2时,程序1停止不动,直到程序2完成回到程序1来,程序1才继续下去,这就是同步;如果程序1调用程序2后,自己继续执行下去,那么两者之间就是异步。很明显多线程是一种异步执行方式,Windows系统的SendMessagePostMessage分别是同步和异步方式。

一个TRTLCriticalSection类型的变量代表了一个临界区,引用《Windows高级编程》中的话,临界区就好像一个只能进一人的洗手间,后面的人只能排队等到进去的人出来才能进去。让我讲得详细一点,假设你有一个数据结构A会被线程BC读写,则你可以声明一个TRTLCriticalSection变量CS,接着调用InitializeCriticalSection(CS),这时你拥有了一个临界区,在BC线程对A进行读写的地方,都套上如下的代码:

EnterCriticalSection(CS);

try

//A进行读写

finally

  LeaveCriticalSection(CS);

end;

你便能保证同一时间只有一个线程对A进行读写。

它的过程是这样的:

线程BC一直在运行,某一时刻,BC同时要对A进行操作,但事实上是不可能同时的,总有一个线程会快一点,假设是B吧,B线程的第一句便是EnterCriticalSection(CS),使得线程B进入了临界区,接着BA进行操作。而操作系统总是这样频繁的在不同线程之间切换以制造“多任务”的假象,当BA的操作进行到一半时,系统将执行权切换给线程C,我们知道C也要对A进行操作,它的第一句也是EnterCriticalSection(CS),可是进这个“洗手间”时发现里面已经有“人”了,C没办法只能一直停在Enter的地方等候,过很短的时间,执行权交给了线程BB继续对A进行操作,如此反复,B终于操作完了,最后调用LeaveCriticalSection(CS)离开“洗手间”,这时执行权再交到C时,C发现B已经离开临界区,它终于可以进入临界区对于A进行操作了,而此时别的线程要对A进行操作,也只能等C离开“洗手间”。

对于临界区有些地方值得注意:

1.      一个TRTLCriticalSection变量代表一个临界区,如果你使用两个这样的变量,则它们是毫不相干的,就像两个“洗手间”一样,对于同一个资源不能起到同步的作用。

2.      不要针对多个资源只声明一个Critical Section,这样使每一时刻只能对一个资源进行读写,会对程序的效率有很大的影响。为每个需要同步保护的资源声明一个临界区变量。

3.      需要对资源进行同步保护,则所有对资源操作的地方都要有EnterLeave函数,且要有Try..finally结构,保证最后能Leave

4.      在临界区里面不要进行非常费时的操作,更重要的防止死锁的发生。

死锁

线程同步虽好,但也带来一些问题,最典型的就是死锁,死锁通常发生在线程之间相互等待的情况下。拿临界区来说,假设有两个临界区,两个线程分别进入一个临界区,接着各自又要进入另一个临界区,这时就会落入“你等我,我等你”的轮回。下面我制造这种场景给你给看看:

unit Unit1;

interface

uses
   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Dialogs, StdCtrls;

const
   WM_MYMESSAGE = WM_USER +
001;

type
   TMyThread1 = class(TThread)
  
protected
    procedure Execute; override;
  end;

   TMyThread2 = class(TThread)
  
protected
    procedure Execute; override;
  end;

   TForm1 = class(TForm)
     Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  
private
    
{ Private declarations }
  public
    
{ Public declarations }
     Thd1: TMyThread1;
     Thd2: TMyThread2;
  end;

var
   Form1: TForm1;
   CS1, CS2: TRTLCriticalSection;
implementation

{$R *.dfm}

procedure DeadLock(var CSA, CSB: TRTLCriticalSection);
begin
   EnterCriticalSection(CSA);
    Sleep(
100);
   EnterCriticalSection(CSB);
  
try
     Sleep(
1000);
  finally
     LeaveCriticalSection(CSA);
     LeaveCriticalSection(CSB);
  end;
end;

{ TMyThread }

procedure TMyThread1.Execute;
begin
   DeadLock(CS1, CS2);
end;

{ TMyThread2 }

procedure TMyThread2.Execute;
begin
   DeadLock(CS2, CS1);
end;

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
begin
   Thd1.Resume;
   Thd2.Resume;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
   InitializeCriticalSection(CS1);
   InitializeCriticalSection(CS2);
   Thd1 := TMyThread1.Create(True);
   Thd2 := TMyThread2.Create(True);
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
   DeleteCriticalSection(CS1);
   DeleteCriticalSection(CS2);
   Thd1.Free;
   Thd2.Free;
end;

end.

运行上面程序,点击按钮1,你的程序没有什么异样,但关闭程序看看,程序关闭不了,因为两个线程落到相互等待的局面,而线程的Free又在等Execute结束,所以程序没有办法关闭了。

让我说说为什么会发生死锁,点击Button1时,两个线程同时启动,TMyThread1执行了DeadLock(CS1, CS2),而TMyThread2执行了DeadLock(CS2, CS1),注意到两个参数倒过来了。

它的过程是这样,首先线程1先执行DeadLock,第一个进入EnterCriticalSection(CSA),注意这里的CSACS1,接下来Sleep(100),在线程1睡觉到一般时,线程2得到执行权也执行DeadLock,它也进入EnterCriticalSection(CSA),这里的CSA却是CS2,接着线程2Sleep(100),也是睡到一半,换到线程1,线程1睡醒后往下执行,它要EnterCSB,对于线程1来说,CSBCS2,但CS2已经被线程2Enter了,所以线程1挂起等待线程2离开CS2临界区;接着线程2醒来要进入EnterCriticalSection(CSB),这里的CSBCS1,但CS1给线程1进入了,所以线程2也挂起等待线程1离开CS1临界区。就这样,两个线程相互等待,没完没了。

这就是死锁发生的情况之一,当多个临界区一起工作时,死锁就有可能发生,这是要特别注意的,在死锁可能发生的地方,尽量只用一个临界区。

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值