delphi关于多线程同步的一些用法Word格式.docx
- 文档编号:20732090
- 上传时间:2023-01-25
- 格式:DOCX
- 页数:9
- 大小:21.09KB
delphi关于多线程同步的一些用法Word格式.docx
《delphi关于多线程同步的一些用法Word格式.docx》由会员分享,可在线阅读,更多相关《delphi关于多线程同步的一些用法Word格式.docx(9页珍藏版)》请在冰豆网上搜索。
ListBox2:
Button1:
TButton;
procedureButton1Click(Sender:
TObject);
private
{Privatedeclarations}
public
{Publicdeclarations}
end;
//定义线程类
TListThread=class(TThread)
private
Str:
String;
protected
procedureAddToList;
//将Str加入ListBox组件
ProcedureExecute;
override;
LBox:
TListBox;
//定义一个线程内公共变量,接收从进程传进来的TListBox;
//定义变量
var
Form1:
TForm1;
Letters:
String='
AAAAAAAAAAAAAAAAAAAA'
;
//全局变量
implementation
{$R*.dfm}
//线程类实现部分
procedureTListThread.Execute;
I,J,K:
Integer;
begin
fori:
=0to50do
begin
forJ:
=1to20do
forK:
=1to1000do//循环1000次增加产生冲突的几率
ifletters[j]<
'
Z'
then
letters[j]:
=succ(Letters[j])
else
='
A'
str:
=letters;
synchronize(addtolist);
//同步访问VCL可视组件(本例中,这个同步可以不要,见Button1Click);
end;
procedureTListThread.AddToList;
LBox.Items.Add(str);
//将str加入列表框
//窗口类实现部分
procedureTForm1.Button1Click(Sender:
th1,th2:
TListThread;
Listbox1.Clear;
Listbox2.Clear;
th1:
=tlistThread.Create(true);
//创建线程1
th2:
//创建线程2
th1.LBox:
=listBox1;
//不同的VCL控件;
th2.LBox:
=listBox2;
th1.Resume;
//开始执行
th2.Resume;
end.
由上例可见,当多个线程同时修改一个公用变量时,会产生冲突,所以我们要设法防止它,这样我们开发的多线程应用才能够稳定地运行。
下面我们来改进它。
我们先使用临界段来串行化,实现同步。
在上例unit1.pas代码的uses段中加入SyncObjs单元,
加入全局临界段变量:
Critical1:
TRTLCriticalSection
在FormCreate事件中加入:
InitializeCriticalSection(Critical1)这句代码,
在FormDestroy事件中加入DeleteCriticalSection(Critical1)这句代码,
然后修改TListThread.Execute函数,修改后的代码似如下所示(?
处为增加的代码):
I,J,K:
fori:
begin
//进入临界段
EnterCriticalSection(Critical1);
forJ:
forK:
=1to3000do
letters[j]:
else
letters[j]:
str:
//退出临界段
LeaveCriticalSection(Critical1);
synchronize(addtolist);
好了,重新编译,运行结果如下图所示(略)
程序成功的避免了冲突,看来真的很简单,我们成功了!
当然我们还可以使用其它同步技术如Mutex(互斥对象),Semaphore(信号量)等,这些技术都是Windows通过API直接提供给我们的。
下面总结一下Windows常用的几种线程同步技术。
1.CriticalSections(临界段),源代码中如果有不能由两个或两个以上线程同时执行的部分,可以用临界段来使这部分的代码执行串行化。
它只能在一个独立的进程或一个独立的应用程序中使用。
使用方法如下:
//在窗体创建中
InitializeCriticalSection(Critical1)
//在窗体销毁中
DeleteCriticalSection(Critical1)
//在线程中
EnterCriticalSection(Critical1)
……保护的代码
LeaveCriticalSection(Critical1)
2.Mutex(互斥对象),是用于串行化访问资源的全局对象。
我们首先设置互斥对象,然后访问资源,最后释放互斥对象。
在设置互斥对象时,如果另一个线程(或进程)试图设置相同的互斥对象,该线程将会停下来,直到前一个线程(或进程)释放该互斥对象为止。
注意它可以由不同应用程序共享。
hMutex:
=CreateMutex(nil,false,nil)
CloseHandle(hMutex)
WaitForSingleObject(hMutex,INFINITE)
ReleaseMutex(hMutex)
3.Semaphore(信号量),它与互斥对象相似,但它可以计数。
例如可以允许一个给定资源同时同时被三个线程访问。
其实Mutex就是最大计数为一的Semaphore。
使用方法如下:
hSemaphore:
=CreateSemaphore(nil,lInitialCount,lMaximumCount,lpName)
CloseHandle(hSemaphore)
WaitForSingleObject(hSemaphore,INFINITE)
ReleaseSemaphore(hSemaphore,lReleaseCount,lpPreviousCount)
4.还可以使用Delphi中的TcriticalSection这个VCL对象,它的定义在Syncobjs.pas中。
当你开发多线程应用时,并且多个线程同时访问一个共享资源或数据时,你需要考虑线程同步的问题了。
delphi中多线程同步的一些方法
[2006-01-0910:
48:
03|作者:
snox字体大小:
大|中|小]
当有多个线程的时候,经常需要去同步这些线程以访问同一个数据或资源。
例如,假设有一个程序,其中一个线程用于把文件读到内存,而另一个线程用于统计文件中的字符数。
当然,在把整个文件调入内存之前,统计它的计数是没有意义的。
但是,由于每个操作都有自己的线程,操作系统会把两个线程当作是互不相干的任务分别执行,这样就可能在没有把整个文件装入内存时统计字数。
为解决此问题,你必须使两个线程同步工作。
存在一些线程同步地址的问题,Win32提供了许多线程同步的方式。
在本节你将看到使用临界区、互斥、信号量和事件来解决线程同步的问题。
1.临界区
临界区是一种最直接的线程同步方式。
所谓临界区,就是一次只能由一个线程来执行的一段代码。
如果把初始化数组的代码放在临界区内,另一个线程在第一个线程处理完之前是不会被执行的。
在使用临界区之前,必须使用InitializeCriticalSection()过程来初始化它。
其声明如下:
procedureInitializeCriticalSection(var
lpCriticalSection参数是一个TRTLCriticalSection类型的记录,并且是变参。
至于TRTLCriticalSection是如何定义的,这并不重要,因为很少需要查看这个记录中的具体内容。
只需要在lpCriticalSection中传递未初始化的记录,InitializeCriticalSection()过程就会填充这个记录。
注意Microsoft故意隐瞒了TRTLCriticalSection的细节。
因为,其内容在不同的硬件平台上是不同的。
在基于Intel的平台上,TRTLCriticalSection包含一个计数器、一个指示当前线程句柄的域和一个系统事件的句柄。
在Alpha平台上,计数器被替换为一种Alpha-CPU数据结构,称为spinlock。
在记录被填充后,我们就可以开始创建临界区了。
这时我们需要用EnterCriticalSection()和LeaveCriticalSection()来封装代码块。
这两个过程的声明如下:
procedureEnterCriticalSection(varlpCriticalSection:
TRRLCriticalSection);
stdcall;
procedureLeaveCriticalSection(varlpCriticalSection:
正如你所想的,参数lpCriticalSection就是由InitializeCriticalSection()填充的记录。
当你不需要TRTLCriticalSection记录时,应当调用DeleteCriticalSection()过程,下面是它的声明:
procedureDeleteCriticalSection(varlpCriticalSection:
2.互斥
互斥非常类似于临界区,除了两个关键的区别:
首先,互斥可用于跨进程的线程同步。
其次,互斥能被赋予一个字符串名字,并且通过引用此名字创建现有互斥对象的附加句柄。
提示临界区与事件对象(比如互斥对象)的最大的区别是在性能上。
临界区在没有线程冲突时,要用10~15个时间片,而事件对象由于涉及到系统内核要用400~600个时间片。
可以调用函数CreateMutex()来创建一个互斥量。
下面是函数的声明:
functionlpMutexAttributes参数为一个指向TSecurityAttributtes记录的指针。
此参数通常设为0,表示默认的安全属性。
bInitalOwner参数表示创建互斥对象的线程是否要成为此互斥对象的拥有者。
当此参数为False时,表示互斥对象没有拥有者。
lpName参数指定互斥对象的名称。
设为nil表示无命名,如果参数不是设为nil,函数会搜索是否有同名的互斥对象存在。
如果有,函数就会返回同名互斥对象的句柄。
否则,就新创建一个互斥对象并返回其句柄。
当使用完互斥对象时,应当调用CloseHandle()来关闭它。
在程序中使用WaitForSingleObject()来防止其他线程进入同步区域的代码。
此函数声明如下:
(**************************************以下尚未整理*********************************)
function
这个函数可以使当前线程在dwMilliseconds指定的时间内睡眠,直到hHandle参数指定的对象进入发信号状态为止。
一个互斥对象不再被
线程拥有时,它就进入发信号状态。
当一个进程要终止时,它就进入发信号状态。
dwMilliseconds参数可以设为0,这意味着只检查hHandle参
数指定的对象是否处于发信号状态,而后立即返回。
dwMilliseconds参数设为INFINITE,表示如果信号不出现将一直等下去。
这个函数的返回值如下
WaitForSingleObject()函数使用的返回值
返回值含义
WAIT_ABANDONED指定的对象是互斥对象,并且拥有这个互斥对象的线程在没有释放此对象之前就已终止。
此时就称互斥对象被抛弃。
这
种情况下,这个互斥对象归当前线程所有,并把它设为非发信号状态
WAIT_OBJECT_0指定的对象处于发信号状态
WAIT_TIMEOUT等待的时间已过,对象仍然是非发信号状态再次声明,当一个互斥对象不再被一个线程所拥有,它就处于发信号状态。
此时
首先调用WaitForSingleObject()函数的线程就成为该互斥对象的拥有者,此互斥对象设为不发信号状态。
当线程调用ReleaseMutex()函数并传
递一个互斥对象的句柄作为参数时,这种拥有关系就被解除,互斥对象重新进入发信号状态。
注意除WaitForSingleObject()函数外,你还可以使用WaitForMultipleObject()和MsgWaitForMultipleObject()函数,它们可以等待几个
对象变为发信号状态。
这两个函数的详细情况请看Win32API联机文档。
3.信号量
另一种使线程同步的技术是使用信号量对象。
它是在互斥的基础上建立的,但信号量增加了资源计数的功能,预定数目的线程允许同时进
入要同步的代码。
可以用CreateSemaphore()来创建一个信号量对象,其声明如下:
和CreateMutex()函数一样,CreateSemaphore()的第一个参数也是一个指向TSecurityAttributes记录的指针,此参数的缺省值可以设为
nil。
lInitialCount参数用来指定一个信号量的初始计数值,这个值必须在0和lMaximumCount之间。
此参数大于0,就表示信号量处于发信号状
态。
当调用WaitForSingleObject()函数(或其他函数)时,此计数值就减1。
当调用ReleaseSemaphore()时,此计数值加1。
参数lMaximumCount指定计数值的最大值。
如果这个信号量代表某种资源,那么这个值代表可用资源总数。
参数lpName用于给出信号量对象的名称,它类似于CreateMutex()函数的lpName参数。
★★★关于线程同步:
Synchronize()是在一个隐蔽的窗口里运行,如果在这里你的任务很繁忙,你的主窗口会阻塞掉;
Synchronize()只是将该线程的代码放到
主线程中运行,并非线程同步。
临界区是一个进程里的所有线程同步的最好办法,他不是系统级的,只是进程级的,也就是说他可能利用进程内的一些标志来保证该进程
内的线程同步,据Richter说是一个记数循环;
临界区只能在同一进程内使用;
临界区只能无限期等待,不过2k增加了
TryEnterCriticalSection函数实现0时间等待。
互斥则是保证多进程间的线程同步,他是利用系统内核对象来保证同步的。
由于系统内核对象可以是有名字的,因此多个进程间可以利用
这个有名字的内核对象保证系统资源的线程安全性。
互斥量是Win32内核对象,由操作系统负责管理;
互斥量可以使用WaitForSingleObject实
现无限等待,0时间等待和任意时间等待。
如果把初始化数组的代码放在临界区内
,另一个线程在第一个线程处理完之前是不会被执行的。
在使用临界区之前,必须使用InitializeCriticalSection()过程来初始化它。
在第一个线程调用了EnterCriticalSection()之后,所有别的线程就不能再进入代码块。
下一个线程要等第一个线程调用
LeaveCriticalSection()后才能被唤醒。
其次,互斥能被赋予一个字符串名字,并且通过引
用此名字创建现有互斥对象的附加句柄。
提示:
临界区与事件对象(比如互斥对象)的最大的区别是在性能上。
临界区在没有线程冲突时,要用10~15个时间片,而事件对象由于
涉及到系统内核要用400~600个时间片。
当一个互斥对象不再被一个线程所拥有,它就处于发信号状态。
此时首先调用WaitForSingleObject()函数的线程就成为该互斥对象的拥有
者,此互斥对象设为不发信号状态。
当线程调用ReleaseMutex()函数并传递一个互斥对象的句柄作为参数时,这种拥有关系就被解除,互斥对
象重新进入发信号状态。
可以调用函数CreateMutex()来创建一个互斥量。
当使用完互斥对象时,应当调用CloseHandle()来关闭它。
可以用CreateSemaphore()来创建一个信号量对象,
因为只允许一个线程进入要同步的代码,所以信号量的最大计数值(lMaximumCount)要设为1。
ReleaseSemaphore()函数将使信号量对象的
计数加1;
记住,最后一定要调用CloseHandle()函数来释放由CreateSemaphore()创建的信号量对象的句柄。
★★★WaitForSingleObject函数的返值:
WAIT_ABANDONED指定的对象是互斥对象,并且拥有这个互斥对象的线程在没有释放此对象之前就已终止。
这种
情况下,这个互斥对象归当前线程所有,并把它设为非发信号状态;
WAIT_OBJECT_0指定的对象处于发信号状态;
WAIT_TIMEOUT等待的时间已过,对象仍然是非发信号状态;
——————————————————————————————————————————————
VCL支持三种技术来达到这个目的:
(2)使用critical区
如果对象没有提高内置的锁定功能,需要使用critical区,Critical区在同一个时间只也许一个线程进入。
为了使用Critical区,产生一
个TCriticalSection全局的实例。
TcriticalSection有两个方法,Acquire(阻止其他线程执行该区域)和Release(取消阻止)
每个Critical区是与你想要保护的全局内存相关联。
每个访问全局内存的线程必须首先使用Acquire来保证没有其他线程使用它。
完成以
后,线程调用Release方法,让其他线程也可以通过调用Acquire来使用这块全局内存。
警告:
Critical区只有在所有的线程都使用它来访问全局内存,如果有线程直接调用内存,而不通过Acquire,会造成同时访问的问题。
例如:
LockXY是一个全局的Critical区变量。
任何一个访问全局X,Y的变量的线程
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- delphi 关于 多线程 同步 一些 用法
![提示](https://static.bdocx.com/images/bang_tan.gif)