分享
 
 
 

指南:工作者线程和信号量(续)

王朝other·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

http://www.thebits.org/tutorials/mjsema1.asp

指南:工作者线程和信号量

©Malcolm Smith, 14th October 2002

创建工作者线程并等待它们结束

那么在是何种境况下您可以创建使应用程序冻结直到所有的线程完成了它们的处理的线程呢?

通常的情景是您的应用程序开始多线程,在转到下一个独立的任务前,需要等待所有的这些线程完成时。

本文我将使用和前面一样的线程类,创建一些实例并等待它们结束。

下面我进行深入讨论(一些代码将被省略):

for(int i = 0; i < 10; i++)

{

Counter++;

LabelIndex = (Counter-1) % 6;

TWorkerThread *Thread = new TWorkerThread(true, LabelArray[LabelIndex], 50);

Thread->FreeOnTerminate = true;

Thread->Resume();

}

上面同样的代码创建10个线程(现在请忽略它们都有同样标签的事实)

我们无法得知所有的线程何时全部完成。这可以通过在另外的线程中创建这些独立的线程,使用一个知道有多少当前运行线程的ThreadCounter来实现。

这就是上面的线程类被称为工作者线程的地方。下面我创建一个主人线程,通过该线程创建这些工作者线程,使用计数器去判断它们何时都结束了。

注意,这里最初的代码将会导致前面提到的死锁。更正的版本稍后会涉及。

这是类定义:

class THostThread : public TThread

{

private:

int Counter;

int MaxThreads;

TLabel* LabelArray[6];

protected:

void __fastcall Execute(void);

void __fastcall HasCompleted(TObject *Sender);

public:

__fastcall THostThread(bool CreateSuspended, TLabel* ALabelArray[6],

int AMaxThreads);

};

此刻先别管 AmaxThreads参数。

该类将创建10个线程,并等待它们全部完成。实现如下:

__fastcall THostThread::THostThread(bool CreateSuspended, TLabel *ALabel,

int AMaxThreads) : TThread(CreateSuspended), pLabel(ALabel)

{

Counter = 0;

MaxThreads = AMaxThreads;

memcpy(LabelArray, ALabelArray, sizeof(TLabel*) * 6);

}

void __fastcall THostThread::Execute(void)

{

int LabelIndex;

// 创建10个线程 ,增加我们的计数器

for(int i = 0; i < 10; i++)

{

Counter++;

LabelIndex = (Counter-1) % 6;

TWorkerThread *Thread = new TWorkerThread(true, LabelArray[LabelIndex], 50);

Thread->FreeOnTerminate = true;

Thread->OnTerminate = HasCompleted; // method used to decrement counter

Thread->Resume();

}

// 等待所有线程结束

while(!Terminated && Counter > 0)

Sleep(1);

}

void __fastcall THostThread::HasCompleted(TObject *Sender)

{

Counter--;

}

现在代码看起来很好,Execute方法创建10个线程,每个线程结束后让计数器减1。Execute方法直到所有的线程都结束才返回。

完整起见,您可能需要这样实现这个线程的创建。

THostThread *Thread = new THostThread(true, LabelArray, 3);

Thread->FreeOnTerminate = true;

Thread->Resume();

Thread->WaitFor();

猜猜会发生什么?您的应用程序在最后一行会产生难挨的中断。因为主VCL线程被挂起直至主人线程“Thread”返回。工作者线程仍然尝试通过同步去更新主VCL线程(此时是挂起的)的标签。当您根本不用同步但又使用OnTerminate 事件时类似的问题也会发生。这个事件被主VCL线程的上下文调用从而导致同样的问题。

那么您如何克服这个问题呢?

基本上,您需要设计您的GUI(或者业务对象),可以让它继续处理消息,但是它要知道在您所创建的线程告知它时才做相应的操作。

下面我展示一下在一个GUI应用程序中使用TActionList如何这么做。

演示程序中标名“Run Host Thread”的按纽是和ActionList组件关联的。当您双击这个动作列表组件时,您会看到HostThreadButton项。我使用它的唯一目的就是激活或禁用上面提到的按钮。在一个大的应用程序中您可能还有您的主人线程运行时需要禁用的菜单项或者其他控件。将这些控件都分配给这个动作,您可以通过一行代码去激活或禁用它们。(例如:)

HostThreadButton->Enabled = false;

上例中,所有连接到这个动作的控件都将被禁用。

演示程序中的按纽自身被btnHostThread调用,它的Action属性指向HostThreadButton。这样当HostThreadButton被禁用,连接它的按钮同时被禁用。如果您看btnHostThread按钮的OnClick事件,您会看到没有指定事件处理。那么当按钮被按下时,代码是如何执行的呢?

我先前声明这个按钮的action属性制向HostThreadButton动作。您看这个动作,它有一个OnExecute事件,无论何时点击按钮,这个方法都会被调用。 如果我们有和这个动作关联的菜单项,那么菜单项被选上时同样的代码也会运行。

这是这个动作的OnExecute事件的部分代码(没有注解)

THostThread *Thread = new THostThread(true, LabelArray, 3);

Thread->FreeOnTerminate = true;

Thread->OnTerminate = HostThreadHasCompleted;

Thread->Resume();

HostThreadButton->Enabled = false;

这时,我们已经通知主人线程在它结束时去调用HostThreadHasCompleted方法。猜猜下面我们要做什么?

void __fastcall TForm1::HostThreadHasCompleted(TObject *Sender)

{

HostThreadButton->Enabled = true;

}

这么做没有奖品。 如果您运行演示程序的这部分,您会看到6个标签被更新,按钮最初被禁用,当所有的线程完成后被重新激活。

由于演示程序的简单,您看不到10个线程在运行,因为我们只有6个标签.

下面我们继续进一步修改代码。让它变成这个样子:让10个线程最终都运行,但同时最多只有3个线程在运行。我们的按钮直到10个线程完全完成才能被激活。

我们要添加一个信号量到我们的主人类。信号量是个同步对象,我们用它来限制创建的线程的数目。这个对象通过处理它自己的计数器来工作。任何时候,计数器的值大于0就允许执行线程,当计数器等于0时,线程会中止,直到一个工作者线程结束增加了信号量的计数器。

请这么做。

添加信号量句柄到THostThread类,在构造时创建这个信号量。

hSemaphore = CreateSemaphore(NULL, 3, 3, NULL);

这创建了一个初始量为3、最大数目为3的信号量。接着,我们修改这个线程类的Execute方法,如下:(略去演示程序中的 try/__finally 块)

void __fastcall THostThread::Execute(void)

{

int LabelIndex = 0;

for(int i = 0; i < 10; i++)

{

Counter++;

LabelIndex = LabelIndex % 6;

::WaitForSingleObject(hSemaphore, INFINITE);

TWorkerThread *Thread = new TWorkerThread(true, LabelArray[LabelIndex], 50);

Thread->FreeOnTerminate = true;

Thread->OnTerminate = HasCompleted; // 用以减小计数器的方法

Thread->Resume();

LabelIndex++;

}

while(!Terminated && Counter > 0)

Sleep(1);

CloseHandle(hSemaphore);

}

自然,每个线程结束时调用的方法也要做相应变动。

void __fastcall THostThread::HasCompleted(TObject *Sender)

{

Counter--;

::ReleaseSemaphore(hSemaphore, 1, NULL);

}

这代码用以指引标签数组中下一项被修改,因为计数器的值从来不会大于2。

调用WaitForSingleObject减少信号量处理的计数器。只要这个计数器大于0,线程就会继续执行。

最初的循环创建了3个工作者线程,信号量计数器等于0,然后线程的执行停在WaitForSingleObject那里。

任何一个工作者线程结束,HasCompleted方法被调用。ReleaseSemaphore调用再次增加信号量计数器从而允许主人线程继续执行。另一个工作者线程被创建。停止/继续/停止这样的过程一直进行直至最终所有10个工作者线程都被创建并完全执行。当最后的线程结束时,Execute方法完成,GUI的按钮再次被激活。

运行演示程序,您会看到先是前面3个标签被更新,接着是后面3个,再接着是前面3个,最后是第4个(正好10个线程)。

一旦您明白了,它就什么都不是了。

摘要

这篇短文向您展示了如何创建简单的线程,并与主VCL线程同步事件。接着我们在一个主人线程中创建一些工作者线程,展示如何避免您的应用程序死锁(哦,至少使您知道了典型的原因)。

最后,给出了一种使用信号量控制运行中的线程数的方法。

致礼

第1页 | 下载源代码

(翻译:01soft)

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有