郁闷之线程同步:该死的CriticalSection(0分)

  • 主题发起人 主题发起人 Another_eYes
  • 开始时间 开始时间
A

Another_eYes

Unregistered / Unconfirmed
GUEST, unregistred user!
先看一个简单的例子:
type
ttest = class(TThread)
protected
procedure Execute;
override;
end;

var
c: TRTLCriticalSection;
implementation
procedure ttest.Execute;
begin
while not terminateddo
begin
sleep(5000);
entercriticalsection(c);
showmessage('aaa');
leavecriticalsection(c);
end;
end;

form上放一个button, OnClick事件代码:
procedure TForm1.Button1Click(Sender: TObject);
begin
initializecriticalsection(c);
entercriticalsection(c);
ttest.create(false);
leavecriticalsection(c);
sleep(1000);
deletecriticalsection(c);
end;

运行一下,点button,会发生什么? 什么也没发生? 嗬嗬,那只意味着一件事:线程死翘翘了。 事实上观察资源管理器可以发现线程已经终止了。
是不是和我有一样的感慨:
不就是等待了一个已经关闭掉的criticalsection嘛,至于杀掉我的线程吗? 郁闷。
而更郁闷的是windows倒是没杀掉我的线程,不过是线程彻底睡去了而已, 最后连程序都关不掉。
话说有一天我做了一个完成端口的程序,开了一堆工作线程处理completionport的notify,为了避免运行时每个socket读写中的冲突,我照正常思维如下定义了一个client:
TClientSocket = class
private
FLock: TRTLCriticalSection;
....
public
constructor Create;
// 调用InitializeCriticalSection初始化FLock
destructor Destroy;
override;
// 调用DeleteCriticalSection释放资源
procedure Lock;
// 调用EnterCriticalSection;
procedure Unlock;
// 调用LeaveCriticalSection;
....
end;

结果发觉运行了一段时间(一段时间≈2分钟--2天半)后程序彻底死翘翘了。郁闷之极地找了2个礼拜的"死锁"而一无所获。无意中察看资源管理器发觉线程数中途减少了,经过上述测试又察看了msdn(不幸我原来没着玩意,该死的delphi的win32帮助中对这个关键点只字不提--即deletecriticalsection释放之后,那些正在等待这个criticalsection的线程的状态成未定义)才发觉原来是EnterCriticalSection在搞鬼。
然后开始思考如何避免这个问题,因为可能一个线程执行socket的write为了不使发送数据紊乱必须加锁输出缓冲区,而在加锁前windows切换线程,执行另一个线程中socket的close请求,执行完后该死的criticalsection已经释放了,等再次切换回前一个线程时那个线程的末日也就到了。考虑了很久都不得其法(包括建一个全局的criticalsection池,这个也会存在问题,具体会出现什么问题你可以想想,呵呵,实践证明不可用),因为我无法确定用户到底什么时候会退出,而执行完退出代码就可能造成某个等待的线程完蛋。
一怒之下抛弃criticalsection自己写了一个lock总算解决了问题:
type
TClient = class
private
FLock: Integer;
FLockCnt: Integer;
.....
end;

function TClient.Lock: Boolean;
var
n, m: Integer;
c: Cardinal;
begin
try
n := GetCurrentThreadID;
result := false;
c := GetTickCount;
while not result and Activedo
begin
m := Integer(InterLockedCompareExchange(Pointer(FLock), Pointer(n), nil));
result := (m=0) or (m=n);
if result then
Inc(FLockCnt)
else
begin
if GetTickCount-c >1000 then
log('Maybe DeadLock in thread:'+inttostr(n)+' wait for locks locked by thread:'+inttostr(m));
sleep(0);
end;
end;
except
result := false;
end;
end;

procedure Unlock;
begin
try
if InterLockedDecrement(FLockCnt) = 0 then
InterLockedExchange(FLock, 0);
except
end;
end;
 
一般是这样用的。(我这样用)
在一个线程的全局中加入一个 TCriticalSection 在创建时创建它。
另一个线程在创建时直接把这个赋给它内部的一个 TCriticalSection
然后就像用自身变量那样用。
在访问临界对象前用 Enter
结束时用 Leave
不过这个我用得比较少我用 Event 用得更多一点。
它可以判断超时。

 
我一般在全局创建CriticalSection,在程序退出的时候Delete这个CriticalSection,然后在线程中使用这个CriticalSection。
CriticalSection最好是全局的,它并没有占用太多的系统资源。我建议你不要在TClientSockt创建CriticalSection,也不要在TClientSockt删除它,把它做成全局的,试试。
一点愚见,如有错误请指正!
 
对头,我用ENTER进入方法也是不成功的,换成以下则OK
var
ThreadL:TCriticalSection;
begin
if FPrepareThread< FThreadCount then
FPrepareThread := FThreadCount ;
if FPrepareThread > FTotalThread then
begin
Terminate;
end
else
begin
ThreadL := TCriticalSection.Create;
ThreadL.Acquire;
try
FPrepareThread:=FPrepareThread+1;
//锁定变量
finally
ThreadL.Release;
end;
CreateNewGet((FPrepareThread-1)*FPacketRec+1,FPacketRec);;
end;
 
不是,
你不要在主线程中调用entercriticalsection就行了.
安全起见,你做一个enter与Leave方法,检查执行线程是不是主线程,是主线程就直接返回.
Delphi的主线程ID是个全局变量,记不太清了,好象是mainthreadId.
这个问题我以前就碰到了.
 
to hexenzhou, 我也有试过全局的criticalsection, 不过请注意一点: 不是一个criticalsection而是成千个,明白问题所在了吗? 这样在每个socket中必须存在一个所对应的criticalsection的引用, 最常见是用个PRTLCriticalSection指针吧,问题就在于对象可能中途被某个线程释放,这样某些等待的线程调用entercriticalsection或leavecriticalsection时传的参数可能就是一个非法指针了,一样存在问题。
to lynu: windows并没有对主线程进行特别处理, 即主线程运行时并没有禁止其他线程的运行,所以写在主线程中的代码一样和子线程存在冲突。 主线程中不调用entercriticalsection岂不和所有地方都不调用entercriticalsection一样不安全?
 
呵呵,windows并没有对主线程进行特别处理,但你用的并非windows的线程ID,delphi可是对TThread作了特别处理,是在System.Pas和classes.pas中,你可以去看看,我略看过,不过也没仔细看,具体还说得不清楚.
反正我自己是这样用的,没有什么问题.安全得很.倒是要区分好哪些代码会被主线程之外的线程调用.
 
你自己程序设计的问题!我在多线程中用criticalsection好好的,一切正常!!
 
来自:masm, 时间:2004-3-29 20:39:50, ID:2528707
你自己程序设计的问题!我在多线程中用criticalsection好好的,一切正常!!
同意楼上,我的还开1000个线程呢
 
牛!牛就一个字!这么复杂也能探出头理出思路,刚刚参加一个多线程讨论,也是这个东西
好一阵我想不通,假如在线程执行的时候需要对某东西操作,在线程退出的时候还要对这东
西操作一下,等于 2 面都要 Lock 一下,否则定死、但不定时。
 
哪有这样用线程和临界区的,
不是找死嘛
所有同步对象,最好在线程不再使用其时,进行删除
一般是先删线程,再删同步对象
或者,唤醒线程后再删
 
后退
顶部