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

原创 2003年04月02日 09:01:00

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

 <?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

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

©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个线程,每个线程结束后让计数器减1Execute方法直到所有的线程都结束才返回。

 

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

 

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

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

http://www.thebits.org/tutorials/mjsema.asp 指南:工作者线程和信号量©Malcolm Smith, 14th October 2002     和我们的所有...
  • 01Soft
  • 01Soft
  • 2003年04月02日 09:01
  • 1023

UI线程和工作者线程

线程分为UI线程和工作者线程,UI线程有窗口,窗口自建了消息队列,这个UI线程维护“消息队列”,“消息队列”是界面线程和工 作者线程的最大区别。所以有用户界面的一般称为UI线程,没有界面的称...
  • libaineu2004
  • libaineu2004
  • 2014年10月23日 15:36
  • 1721

进程与线程的区别,及用户界面线程和工作者线程的创建x线程类

进程:是由两个部分组成,分别是进程的内核对象和地址空间。 线程: 也是由两个部分组成,分别是线程的内核对象和线程堆栈。            进程从来不执行任务,他只是线程的容器。线程总是在进程中...
  • jianjian111dd
  • jianjian111dd
  • 2015年08月28日 13:41
  • 507

进程的信号量和线程的信号量

学习多进程的同步与互斥,和多线的同步与互斥时,发现他们都有sem信号量,很困惑就查了一下区别才发现: 信号量分为有名与无名 信号量在进程是以有名信号量进行通信的,在线程是以无名...
  • diediexiaomi
  • diediexiaomi
  • 2017年08月05日 15:52
  • 229

多线程编程之创建工作者线程

由于之前项目用到了多线程,但每怎么看MSDN的文档,今天复习便仔细看了下,全文翻译如下: 工作者线程: 工作者线程通常用来在后台执行一些用户不需要等待的任务,如一些比较耗时的数学计算(我项目中耗时...
  • mayo2012
  • mayo2012
  • 2015年03月17日 09:42
  • 724

进程/线程通信_信号量与互斥锁_笔记

信号量与普通整型变量区别: 信号量(semaphore)是非负整型变量,除了初始化之外,它只能通过两个标准原子操作:wait(semap) , signal(semap) ; 来进行访问; 操作也被成...
  • benjamin721
  • benjamin721
  • 2016年02月20日 10:05
  • 968

进程与线程,信号量与互斥量的区别

什么是进程? 进程是一个程序正在执行的实例。每个这样的实例都有自己的地址空间和执行状态。 进程有一个PID(Process ID,进程标识),用以区分各个不同的进程。内核记录进程的PID与状态,并...
  • SupreV
  • SupreV
  • 2017年12月09日 21:03
  • 120

MFC 工作者线程

先看一个案例,基于对话框程序,添加两个按钮,按钮1:    “十秒计时”   IDC_COUNT                                                    ...
  • yierdezi
  • yierdezi
  • 2016年10月22日 16:30
  • 372

多线程编程学习3——使用MFC工作者线程

使用MFC工作者线程  工作者线程通常用于后台的需耗费较长时间的工作,例如:计算、后台打印等。创建一个线程专门处理此类工作,用户就可以进行其他工作而无需等待。   使用一个工作线程,首先必须创建它,然...
  • hixixi
  • hixixi
  • 2009年05月20日 16:51
  • 6084

C多线程(三) -- CLR线程池的工作者线程

1. 关于CLR线程池使用ThreadStart与ParameterizedThreadStart建立新线程非常简单,但通过此方法建立的线程难于管理,若建立过多的线程反而会影响系统的性能所以,.NET...
  • u011033906
  • u011033906
  • 2017年03月16日 10:31
  • 460
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:指南:工作者线程和信号量(续)
举报原因:
原因补充:

(最多只允许输入30个字)