锁和对象一起封装的危险
在多线程编写中,绝大多数编码风格喜欢把锁和要访问的对象封装在同一个对象,释放对象的时候也释放锁,这样会造成死锁。我们写一个测试例子,我们创建一个锁,把锁锁住,然后再创建一个线程,一直不停的等待锁返回,然后我们把锁释放,这时线程就死锁,代码如下:
定义接口:
- type
- TLockObject = class;
- TLockTestThread = class;
- TForm1 = class(TForm)
- btn1: TButton;
- mmoLockThreadTest: TMemo;
- procedure btn1Click(Sender: TObject);
- procedure FormCreate(Sender: TObject);
- private
- FLockTestThread: TLockTestThread;
- FLockObject: TLockObject;
- public
- { Public declarations }
- end;
- TLockTestThread = class(TThread)
- private
- FLockObject: TLockObject;
- public
- procedure Execute; override;
- procedure AddLockLog;
- property LockObject: TLockObject read FLockObject write FLockObject;
- end;
- TLockObject = class(TObject)
- private
- FLock: TCriticalSection;
- public
- constructor Create; virtual;
- destructor Destroy; override;
- procedure Lock;
- procedure UnLock;
- end;
- var
- Form1: TForm1;
在Form1创建的时候创建锁,并把锁锁住,创建一个线程等待锁返回:type TLockObject = class; TLockTestThread = class; TForm1 = class(TForm) btn1: TButton; mmoLockThreadTest: TMemo; procedure btn1Click(Sender: TObject); procedure FormCreate(Sender: TObject); private FLockTestThread: TLockTestThread; FLockObject: TLockObject; public { Public declarations } end; TLockTestThread = class(TThread) private FLockObject: TLockObject; public procedure Execute; override; procedure AddLockLog; property LockObject: TLockObject read FLockObject write FLockObject; end; TLockObject = class(TObject) private FLock: TCriticalSection; public constructor Create; virtual; destructor Destroy; override; procedure Lock; procedure UnLock; end; var Form1: TForm1;
- procedure TForm1.FormCreate(Sender: TObject);
- begin
- FLockObject := TLockObject.Create;
- FLockObject.Lock;
- FLockTestThread := TLockTestThread.Create(True);
- FLockTestThread.LockObject := FLockObject;
- FLockTestThread.FreeOnTerminate := True;
- FLockTestThread.Resume;
- end;
线程的执行方法一直等待锁返回,并写一条日志。由于是窗体创建的时候,锁已经锁住了,因此线程会一直等待:procedure TForm1.FormCreate(Sender: TObject); begin FLockObject := TLockObject.Create; FLockObject.Lock; FLockTestThread := TLockTestThread.Create(True); FLockTestThread.LockObject := FLockObject; FLockTestThread.FreeOnTerminate := True; FLockTestThread.Resume; end;
- procedure TLockTestThread.AddLockLog;
- begin
- Form1.mmoLockThreadTest.Lines.Add('Lock')
- end;
- procedure TLockTestThread.Execute;
- begin
- inherited;
- while not Terminated do
- begin
- FLockObject.Lock;
- Synchronize(AddLockLog);
- end;
- end;
这时线程会一直等待,如果我们把FLockObject释放,线程也会一直等待,造成死锁。procedure TLockTestThread.AddLockLog; begin Form1.mmoLockThreadTest.Lines.Add('Lock') end; procedure TLockTestThread.Execute; begin inherited; while not Terminated do begin FLockObject.Lock; Synchronize(AddLockLog); end; end;
- procedure TForm1.btn1Click(Sender: TObject);
- begin
- FLockObject.Lock;
- FLockObject.Free;
- FLockObject := nil;
- end;
procedure TForm1.btn1Click(Sender: TObject); begin FLockObject.Lock; FLockObject.Free; FLockObject := nil; end;
锁和对象分离
有了上面的基础之后,我们就需要把锁和对象分离,在IOCPDemoSvr例子代码中TSocketHandle我们用一个结构体来管理锁和对象,锁在创建之后只有等TSocketHandle释放之后再释放,主要代码是TSocketHandles类,定义单元:
- {* 客户端对应Socket管理对象 *}
- TSocketHandles = class(TObject)
- private
- {* 正在使用列表管理对象 *}
- FList: TList;
- {* 不再使用列表管理对象 *}
- FIdleList: TList;
- {* 锁 *}
- FLock: TCriticalSection;
- {* 获取某一个 *}
- function GetItems(const AIndex: Integer): PClientSocket;
- {* 获取总个数 *}
- function GetCount: Integer;
- {* 清除 *}
- procedure Clear;
- public
- constructor Create; virtual;
- destructor Destroy; override;
- {* 加锁 *}
- procedure Lock;
- {* 解锁 *}
- procedure UnLock;
- {* 添加一个对象 *}
- function Add(ASocketHandle: TSocketHandle): Integer;
- {* 删除 *}
- procedure Delete(const AIndex: Integer); overload;
- procedure Delete(ASocketHandle: TSocketHandle); overload;
- property Items[const AIndex: Integer]: PClientSocket read GetItems; default;
- property Count: Integer read GetCount;
- end;
实现单元:{* 客户端对应Socket管理对象 *} TSocketHandles = class(TObject) private {* 正在使用列表管理对象 *} FList: TList; {* 不再使用列表管理对象 *} FIdleList: TList; {* 锁 *} FLock: TCriticalSection; {* 获取某一个 *} function GetItems(const AIndex: Integer): PClientSocket; {* 获取总个数 *} function GetCount: Integer; {* 清除 *} procedure Clear; public constructor Create; virtual; destructor Destroy; override; {* 加锁 *} procedure Lock; {* 解锁 *} procedure UnLock; {* 添加一个对象 *} function Add(ASocketHandle: TSocketHandle): Integer; {* 删除 *} procedure Delete(const AIndex: Integer); overload; procedure Delete(ASocketHandle: TSocketHandle); overload; property Items[const AIndex: Integer]: PClientSocket read GetItems; default; property Count: Integer read GetCount; end;
- { TSocketHandles }
- constructor TSocketHandles.Create;
- begin
- FList := TList.Create;
- FIdleList := TList.Create;
- FLock := TCriticalSection.Create;
- end;
- destructor TSocketHandles.Destroy;
- begin
- Clear;
- FList.Free;
- FIdleList.Free;
- FLock.Free;
- inherited;
- end;
- function TSocketHandles.GetItems(const AIndex: Integer): PClientSocket;
- begin
- Result := FList[AIndex];
- end;
- function TSocketHandles.GetCount: Integer;
- begin
- Result := FList.Count;
- end;
- procedure TSocketHandles.Clear;
- var
- i: Integer;
- ClientSocket: PClientSocket;
- begin
- for i := 0 to Count - 1 do
- begin
- ClientSocket := Items[i];
- ClientSocket.Lock.Free;
- ClientSocket.SocketHandle.Free;
- Dispose(ClientSocket);
- end;
- FList.Clear;
- for i := 0 to FIdleList.Count - 1 do
- begin
- ClientSocket := FIdleList[i];
- ClientSocket.Lock.Free; //释放锁
- Dispose(ClientSocket);
- end;
- FIdleList.Clear;
- end;
- procedure TSocketHandles.Lock;
- begin
- FLock.Enter;
- end;
- procedure TSocketHandles.UnLock;
- begin
- FLock.Leave;
- end;
- function TSocketHandles.Add(ASocketHandle: TSocketHandle): Integer;
- var
- ClientSocket: PClientSocket;
- begin
- if FIdleList.Count > 0 then //先在空余中查找
- begin
- ClientSocket := FIdleList[0];
- FIdleList.Delete(0);
- end
- else //否则创建一个
- begin
- New(ClientSocket);
- ClientSocket.Lock := TCriticalSection.Create;
- end;
- ClientSocket.SocketHandle := ASocketHandle;
- ASocketHandle.FLock := ClientSocket.Lock;
- Result := FList.Add(ClientSocket);
- end;
- procedure TSocketHandles.Delete(const AIndex: Integer);
- var
- ClientSocket: PClientSocket;
- begin
- ClientSocket := FList[AIndex];
- ClientSocket.Lock.Enter;
- try
- ClientSocket.SocketHandle.Free;
- ClientSocket.SocketHandle := nil;
- finally
- ClientSocket.Lock.Leave;
- end;
- FList.Delete(AIndex);
- if FIdleList.Count > MAX_IDLELOCK then //如果达到最大空闲个数,则释放
- Dispose(ClientSocket)
- else
- FIdleList.Add(ClientSocket);
- end;
- procedure TSocketHandles.Delete(ASocketHandle: TSocketHandle);
- var
- i, iIndex: Integer;
- begin
- iIndex := -1;
- for i := 0 to Count - 1 do
- begin
- if Items[i].SocketHandle = ASocketHandle then
- begin
- iIndex := i;
- Break;
- end;
- end;
- if iIndex <> -1 then
- begin
- Delete(iIndex);
- end;
- end;
{ TSocketHandles } constructor TSocketHandles.Create; begin FList := TList.Create; FIdleList := TList.Create; FLock := TCriticalSection.Create; end; destructor TSocketHandles.Destroy; begin Clear; FList.Free; FIdleList.Free; FLock.Free; inherited; end; function TSocketHandles.GetItems(const AIndex: Integer): PClientSocket; begin Result := FList[AIndex]; end; function TSocketHandles.GetCount: Integer; begin Result := FList.Count; end; procedure TSocketHandles.Clear; var i: Integer; ClientSocket: PClientSocket; begin for i := 0 to Count - 1 do begin ClientSocket := Items[i]; ClientSocket.Lock.Free; ClientSocket.SocketHandle.Free; Dispose(ClientSocket); end; FList.Clear; for i := 0 to FIdleList.Count - 1 do begin ClientSocket := FIdleList[i]; ClientSocket.Lock.Free; //释放锁 Dispose(ClientSocket); end; FIdleList.Clear; end; procedure TSocketHandles.Lock; begin FLock.Enter; end; procedure TSocketHandles.UnLock; begin FLock.Leave; end; function TSocketHandles.Add(ASocketHandle: TSocketHandle): Integer; var ClientSocket: PClientSocket; begin if FIdleList.Count > 0 then //先在空余中查找 begin ClientSocket := FIdleList[0]; FIdleList.Delete(0); end else //否则创建一个 begin New(ClientSocket); ClientSocket.Lock := TCriticalSection.Create; end; ClientSocket.SocketHandle := ASocketHandle; ASocketHandle.FLock := ClientSocket.Lock; Result := FList.Add(ClientSocket); end; procedure TSocketHandles.Delete(const AIndex: Integer); var ClientSocket: PClientSocket; begin ClientSocket := FList[AIndex]; ClientSocket.Lock.Enter; try ClientSocket.SocketHandle.Free; ClientSocket.SocketHandle := nil; finally ClientSocket.Lock.Leave; end; FList.Delete(AIndex); if FIdleList.Count > MAX_IDLELOCK then //如果达到最大空闲个数,则释放 Dispose(ClientSocket) else FIdleList.Add(ClientSocket); end; procedure TSocketHandles.Delete(ASocketHandle: TSocketHandle); var i, iIndex: Integer; begin iIndex := -1; for i := 0 to Count - 1 do begin if Items[i].SocketHandle = ASocketHandle then begin iIndex := i; Break; end; end; if iIndex <> -1 then begin Delete(iIndex); end; end;