多线程同步

★★★关于线程同步:
Synchronize()是在一个隐蔽的窗口里运行,如果在这里你的任务很繁忙,你的主窗口 会阻塞掉;Synchronize()只是将该线程的代码放到主线程中运行,并非线程同步。
临界区是一个进程里的所有线程同步的最好办法,他不是系统级的,只是进程级的,也就是说他可能利用进程内的一些标志来保证该进程内的线程同步,据Richter说是一个记数循环;临界区只能在同一进程内使用;临界区只能无限期等待,不过2k增加了TryEnterCriticalSection函数实现0时间等待。
互斥则是保证多进程间的线程同步,他是利用系统内核对象来保证同步的。由于系统内核对象可以是有名字的,因此多个进程间可以利用这个有名字的内核对象 保证系统资源的线程安全性。互斥量是Win32 内核对象,由操作系统负责管理;互斥量可以使用WaitForSingleObject实现无限等待,0时间等待和任意时间等待。 
1. 临界区
临界区是一种最直接的线程同步方式。所谓临界区,就是一次只能由一个线程来执行的一段代码。如果把初始化数组的代码放在临界区内,另一个线程在第一个线程处理完之前是不会被执行的。在使用临界区之前,必须使用InitializeCriticalSection()过程来初始化它。 在第一个线程调用了EnterCriticalSection()之后,所有别的线程就不能再进入代码块。下一个线程要等第一个线程调用LeaveCriticalSection()后才能被唤醒。
2. 互斥 
互斥非常类似于临界区,除了两个关键的区别:首先,互斥可用于跨进程的线程同步。其次,互斥能被赋予一个字符串名字,并且通过引用此名字创建现有互斥对象的附加句柄。 
提示:临界区与事件对象(比如互斥对象)的最大的区别是在性能上。临界区在没有线程冲突时,要用10 ~ 15个时间片,而事件对象由于涉及到系统内核要用400~600个时间片。 
当一个互斥对象不再被一个线程所拥有,它就处于发信号状态。此时首先调用WaitForSingleObject()函数的线程就成为该互斥对象的拥有者,此互斥对象设为不发信号状态。当线程调用ReleaseMutex()函数并传递一个互斥对象
的句柄作为参数时,这种拥有关系就被解除,互斥对象重新进入发信号状态。
可以调用函数CreateMutex()来创建一个互斥量。
当使用完互斥对象时,应当调用CloseHandle()来关闭它。
3. 信号量
另一种使线程同步的技术是使用信号量对象。它是在互斥的基础上建立的,但信号量增加了资源计数的功能,预定数目的线程允许同时进入要同步的代码。可以用CreateSemaphore()来创建一个信号量对象, 因为只允许一个线程进入要同步的代码,所以信号量的最大计数值(lMaximumCount) 要设为1。ReleaseSemaphore()函数将使信号量对象的计数加1; 记住,最后一定要调用CloseHandle()函数来释放由CreateSemaphore()创建的信号量对象的句柄。
★★★WaitForSingleObject函数的返值:
WAIT_ABANDONED指定的对象是互斥对象,并且拥有这个互斥对象的线程在没有释放此对象之前就已终止。此时就称互斥对象被抛弃。这种情况下,这个互斥对象归当前线程所有,并把它设为非发信号状态;
   WAIT_OBJECT_0 指定的对象处于发信号状态;
   WAIT_TIMEOUT等待的时间已过,对象仍然是非发信号状态;


问题: 关于线程的简单问题:线程与界面

来自: painboy, 时间: 2005-03-01 12:58:09, ID: 3001075 
当有若干个同类线程对界面的同一个VCL控件进行操作进,必须用Synchronize来同步。
就你的情况来说,设有10个FiveQuery线程进行查询,查询所耗时间约8秒,线程更新界面所需时间大约1秒,这样当你一运行10线程时,前8秒时间界面是可以进行其他操作的,但约8秒后,连续进行10X1即10秒的界面更新,这就会出现10秒左右的“忙沙漏”。
所以,要避免这情况,一是减少并发运行的线程数;二是缩短SynchronizeSource执行时间。优化你的同步更新过程,同时把尽可能的代码写在Execute里。

来自: ppqingyu, 时间: 2005-03-01 17:42:45, ID: 3001730 
呵呵...那像我上面的那些代码还有得救吗?同步的过程全是关于界面更新的呀.
来自: panbq, 时间: 2005-03-06 0:46:20, ID: 3004732 
我碰到的问题跟楼主一样,不过把同步部分的代码写到一个线程的EXECUTE里,在更新的时候,创建此更新线程,界面无响应问题就解决了.

来自: ppqingyu, 时间: 2005-03-06 10:07:03, ID: 3004839 
不是说界面更新在线程是不是安全的吗?这样不会有问题?

来自: panbq, 时间: 2005-03-09 19:28:38, ID: 3009426 
把界面更新的部分在线程里单独写一个过程,如:
procedure Tprocess_data.show_me;
begin
form1.statusbar1.simpletext:=mtt(mtt为线程里的一个局部变量)
end;
在线程的执行过程里这样写:
...处理数据
mtt:=要显示的信息
Synchronize(show_me);

这样同步过程将非常短,同步只是在主界面显示信息而已,而不是把整个处理过程都写进同步过程中去。。。


问题: 是否可以不用Synchronize?

来自: jackchin, 时间: 2004-08-26 11:59:00, ID: 2780104 
procedure TForm1.ChangeCaption(NewCaption: String);
begin
   Label1.Caption := NewCaption
end;
多个线程调用以上的代码, 是否可以不用Synchronize?
即, 不用Synchronize, 访问、更新TLabel的属性是否安全?

来自: 迷糊, 时间: 2004-08-26 12:06:13, ID: 2780123 
多线程更新TLabel的属性肯定不安全,所以要同步,但是同步可以不用Synchronize
比如在子线程中发消息通知主线程更新label

来自: maoyihua, 时间: 2004-08-26 12:23:51, ID: 2780146 
同意楼上的观点,只要是对于界面有关的操作,都需要使用Synchronize来同步;不过使用消息机制也是一个不错的办法。

来自: jackchin, 时间: 2004-08-26 14:40:55, ID: 2780427 
我有一个多线程程序, 偶然会发生不能处理的意外, 估计是线程出问题了
线程除了上述更新界面的操作外,还有 Getmem, FreeMem, PaintBox.Canvas.Paint等
另外, 还有数据库操作(用BDE), 我用以下临界段保护的方法
DBCritical.Enter;
// here is database access code using TQuery,TTable...
DBCritical.Leave;
这些都没有用Synchronize, 不知道是不是有问题

来自: 迷糊, 时间: 2004-08-26 16:42:23, ID: 2780709 
这么频繁的界面操作,你还是考虑一下是不是一定要用多线程
你的临界段保护在主线程中也写了吗?如果没有的话没办法保护。


问题: 怎么用多线程操作listView。

来自: 留香客, 时间: 2004-10-19 20:28:00, ID: 2855263 
我有一个数据库,要把数据全部读入到ListView中。然后进行数据的计算。
ListView中有两个部分:名称和进度。
我在线程中,不停查找与当前名称相符的TlistItem,更新进度的值,如果进度为100%,则删除。
但是即使我用了DoubleBuffer也闪的很厉害,有时候就是一片白色的,还老出错:out of index。我也用BeginUpdate和EndUpdate,就是一片白色。
大侠帮我看看吧。

来自: 陷队之士, 时间: 2004-10-19 20:59:55, ID: 2855320 
我个人认为,之所以能出现你所说的闪烁现象,根本原因不在于你是一个线程,还是几个线程,而在于你处理数据的“场所”。就是说,你的数据如果放在TStringList中处理,处理完后再在ListView中显示出来,可能就会好很多。TStringList与ListView的区别在于,前者只是在背后保存数据、处理数据,而后者不仅要保存数据,更重要的是要显示出来,这就大大限制了速度。
我们在用某些多文件搜索功能时,会发现,在搜索时,如果你让显示搜索结果的窗口缩得很小,或根本就关闭,这时,它会搜索得非常快,反之,将搜索结果窗口弄得很大,它一边搜索,一边添加显示结果,速度就慢很多。
我想,这和你的情况是一个道理。

来自: 刘麻子, 时间: 2004-10-19 21:23:45, ID: 2855359 
能不能用一个线程负责和界面通讯,其他的线程计算,并把进度发给界面通讯线程.
----------------------------
对拉,这个思路很好,多线程就是这样用的。

计算线程都Post消息给通讯线程的话,会不会通讯线程忙不过来而漏掉一些消息呢?
----------------------------
漏掉倒不大会,但是可能不及时,界面线程可适当调用Application.ProcessMessages;


问题: 請教富翁們在多線程中,各個線程能同時去訪問主界面的控件嗎

来自: gemmy, 时间: 2003-02-18 15:35:00, ID: 1629597 
比如,每個線程都要訪問主界面的stringGrid控件但不對它進行編寫

来自: vine, 时间: 2003-02-18 15:45:00, ID: 1629629 
用synchronize()方法来
这个方法会做一些工作,把传入的方法交由主线程来执行

来自: barton, 时间: 2003-02-26 16:02:00, ID: 1645470

有关线程,我谈点看法。
对于大多数非可视控件,线程可以并行安全操作。读写Canvas的VCL通过锁定Canvas来实现
多线程的访问,但是事实上一个线程在访问的时候,另一个线程处于等待状态。实现这种
访问的控制并不需要通过Synchrinize来实现,而是Canvas自己带一个同步对象,实现重入
限制。Synchrinize只能保证同类型的多个实例不会同时执行某个过程,并不能实现多个不
同类线程间的同步控制。
数据库访问应该尽可能避免在线程中访问。Delphi中的TDataset并不是线程安全的,它对
线程的限制比VCL还严格。所以大多数数据库引擎中自己带有同步机制,保证多线程间的同
步。不过,大家知道,应用程序消息循环总是单线程的,基于这个原因,我总是直接通过
消息来同步,又方便又安全。如果是要求即时应答,线程通过SendMessage向父窗口发送消
息,否则通过PostMessage向父窗口发送消息,这样,线程通过PeekMessage循环获得父窗
口返回的消息,必要时还可以在线程中加入事件机制实现线程安全。

来自: shenjian, 时间: 2003-07-26 16:10:00, ID: 2061692

非常赞同barton的看法,对界面控件和非界面控件的交互部分最好使用消息驱动的方式,使用多线程访问界面你不是绝对的高手,你是难以驾驭的。而且通常你那样做反而容易破坏程序良好的结构,导致界面部分和下面捆的很紧。


问题: 使用Synchronize()和使用临界区,互斥对象有何区别?性能差异这么大.为何还要存在互斥同步? ( 积分: 100 )

来自: 淡淡的笑, 时间: 2002-08-13 10:51:00, ID: 1260767 
不是指在使用方法上,而是性能及其它细节方面.(书上没有讲到这个,只讲到临界区与事件对象[互斥
对象]的最大区别是在性能上)

既然临界区比互斥对象的性能要高40倍左右,为何还要存在互斥同步?哪种情况下(必须)使用互斥同步比较好?

来自: 张无忌, 时间: 2002-08-13 11:49:00, ID: 1260893 
Synchronize()是在一个隐蔽的窗口里运行,如果在这里你的任务很繁忙,你的主窗口
会阻塞掉,我做过测试
临界区是一个进程里的所有线程同步的最好办法,他不是系统级的,只是进程级的,
   也就是说他可能利用进程内的一些标志来保证该进程内的线程同步,据Richter说
   是一个记数循环。
互斥则是保证多进程间的线程同步,他是利用系统内核对象来保证同步的。
   由于系统内核对象可以是有名字的,因此多个进程间可以利用这个有名字的内核对象
保证系统资源的线程安全性


问题: 一种程序死亡的方式:SendMessage的后果

主线程和子线程需要交换数据:
主线程这样运行:
1。进入临界区
2。访问全局共享数据
3。离开临界区

子线程如下运行:
1。进入临界区
2。访问全局共享资源
3。调用SendMessage发消息给主线程
4。离开临界区
5。Sleep(100)
6。进行循环,直到主线程退出

这么简单的一个多线程的程序,
竟然经常的死掉,怀疑是SendMessage造成的
在线程中调用那个同步的方法也会出现死锁
因为他也间接调用了SendMessage的方法
所以,这样的多线程模式,慎用SendMessage

来自:daxian, 时间:2004-3-17 23:35:06, ID:2508161
你这么做是找死,SendMessage 的接收消息的窗体的 窗体回调函数 是在主线程内

而 SendMessage 调用后要等待 消息处理的结束后才返回, 如果这时侯主线程等待进入临街区那么,这就是典型的死锁现象。

同步方法虽然也间接调用了 SendMessage 但是Delphi作了巧妙的处理而不会进入死锁,你调用会死锁那是你程序上有错误。

这里你可以用 PostMessage 它不等待消息处理的结束,直接返回

来自:vonrao, 时间:2004-3-17 23:38:57, ID:2508165
如果使用Sendmessage,必须等到接受线程线程跳出才返回,这样有可能造成死锁,我有感与你的观点。一般在通知时我使
用PostMessage或创建一个事件机制,来协调多个线程的同步问题。 

来自:lich, 时间:2004-3-18 20:47:00, ID:2510034
设计多线程时,有一个原则,一定要记住,
就是,锁定的时间尽可能的短,锁定期间做尽可能少的操作,再者,慎用SendMessage 

来自:daxian, 时间:2004-3-18 22:10:58, ID:2510195
我现在没有以前版本的Delphi,在那些版本的Delphi用了CM_EXECPROC消息已进行同步然后再主线程内在作相应处理,如下
function TThread.WaitFor: LongWord;
var
  Msg: TMsg;
  H: THandle;
begin
  H := FHandle;
  if GetCurrentThreadID = MainThreadID then
    while MsgWaitForMultipleObjects(1, H, False, INFINITE,
      QS_SENDMESSAGE) = WAIT_OBJECT_0 + 1 do PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE)
  else WaitForSingleObject(H, INFINITE);
  GetExitCodeThread(H, Result);
end;

在新版本的Delphi内则使用了事件(Event)和临界区(CriticalSection)的配合来进行同步

来自:lich, 时间:2004-3-19 19:12:42, ID:2511613
我又看了一下Classes.pas中TThread类的实现,同时又测试了一下,使用同步方法Synchronize也可能造成死锁

死锁条件如下:主线程进入等待,等待子线程完成本轮操作,
子线程此时调用Synchronize方法,而子线程必须等待Synchronize返回后才能继续执行,此时造成死锁

来自:lich, 时间:2004-3-20 14:03:08, ID:2512394
>>像这样的操作,本身是一种错误,子线程的存在就是为了让主线程不进行等待。

世事无绝对,典型以生产者消费者模式为例,主界面为生产者,人工录入,或由主线程接收数据
交给子线程处理,但是限制了缓冲队列的长度
这样,当缓冲区不够时,主线程必然需要等待,
缓冲区空时,子线程等待,子线程有可能通过同步方法,
将反馈消息显示到主线程的人机界面.,就有可能会出现我所说的
那种死锁条件了

如果不限制缓冲的大小,也不会有这种同步问题了
但是,线程同步也就没有必要讨论了

原文地址:https://www.cnblogs.com/railgunman/p/1869495.html