精华内容
下载资源
问答
  • 本文章介绍Android多线程编程方法及开启步骤
  • c++多线程编程与MFC多线程编程

    千次阅读 2012-03-16 15:52:18
    源代码1:http://download.csdn.net/detail/nuptboyzhb/4160217 源码2:...   ()有关多线程的WIN32 API函数 1、HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes
     
    

    源代码1:http://download.csdn.net/detail/nuptboyzhb/4160217

    源码2:http://download.csdn.net/detail/nuptboyzhb/4161095

     

    (一)有关多线程的WIN32 API函数

    1、HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,                 DWORD dwStackSize, 

    LPTHREAD_START_ROUTINE lpStartAddress,

    LPVOID lpParameter, 

    DWORD dwCreationFlags,

    LPDWORD lpThreadId);

    该函数在其调用进程的进程空间里创建一个新的线程,并返回已建线程的句柄,其中各参数说明如下:

    lpThreadAttributes:指向一个 SECURITY_ATTRIBUTES 结构的指针,该结构决定了线程的安全属性,一般置为 NULL

    dwStackSize:指定了线程的堆栈深度,一般都设置为0

    lpStartAddress:表示新线程开始执行时代码所在函数的地址,即线程的起始地址。一般情况为(LPTHREAD_START_ROUTINE)ThreadFunc,ThreadFunc 是线程函数名

    lpParameter:指定了线程执行时传送给线程的32位参数,即线程函数ThreadFunc的参数;

    dwCreationFlags:控制线程创建的附加标志,可以取两种值。如果该参数为0,线程在被创建后就会立即开始执行;如果该参数为CREATE_SUSPENDED,则系统产生线程后,该线程处于挂起状态,并不马上执行,直至函数ResumeThread被调用

    lpThreadId:该参数返回所创建线程的ID;

    如果创建成功则返回线程的句柄,否则返回NULL。

    2、DWORD SuspendThread(HANDLE hThread);

    该函数用于挂起指定的线程,如果函数执行成功,则线程的执行被终止。

    3、DWORD ResumeThread(HANDLE hThread);

    该函数用于结束线程的挂起状态,执行线程。

    4、VOID  ExitThread(DWORD dwExitCode);

    该函数用于线程终结自身的执行,主要在线程的执行函数中被调用。其中参数dwExitCode用来设置线程的退出码。

    5、BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode);

      一般情况下,线程运行结束之后,线程函数正常返回,但是应用程序可以调用TerminateThread强行终止某一线程的执行。各参数含义如下:

    hThread:将被终结的线程的句柄;

    dwExitCode:用于指定线程的退出码。

      使用TerminateThread()终止某个线程的执行是不安全的,可能会引起系统不稳定;虽然该函数立即终止线程的执行,但并不释放线程所占用的资源。因此,一般不建议使用该函数。

    应与举例:

    简介:本例子是创建三个控制进度条的线程;它们三个的线程函数相同;另外,为了使线程函数的参数传递的信息更多,我们将参数封装为一个结构体;结构体的成员变量,包含了主线程需要向线程函数传递的数据;

    1.       创建一个基于对话框的应用程序。并增加如图所示控件;分别为3个进度条控件关联三个进度条类型的变量;并在对话框的初始化函数中,设定进度条的范围;为编辑框关联一个整型的变量;为12个按钮添加消息处理函数;

    2.       定义结构体:用做线程函数的参数传递

    typedef struct Threadinfo{

    CProgressCtrl *progress;//进度条对象

    int speed;                                     //进度条速度

    int pos;                                 //进度条位置

    } thread,*lpthread;

     

    3.       为对话框增加三个句柄,用于标识各个线程;

    HANDLE hThread1; //线程1线程句柄

    HANDLE hThread2; //线程2线程句柄

            HANDLE hThread3; //线程3线程句柄

    在增加三个结构体类型的变量,用做线程函数的参数传递;

            HANDLE hThread1; //线程1线程句柄

             HANDLE hThread2; //线程2线程句柄

             HANDLE hThread3; //线程3线程句柄

    4.       新增一个静态的全局变量,用于记录所有线程的状态:static int GlobalVar=10000;

    5.       声明并编写线程函数,注意只能有一个参数,且函数的返回值类型也是固定的;函数名可以自定义;

    DWORD WINAPI ThreadFun(LPVOID pthread);//线程入口函数

    6.       在启动按钮的消息处理函数中编写如下代码:

    thread1.progress=&m_progress1;//进度条对象

    thread1.speed=100;//速度

    thread1.pos=0;//初始位置

     hThread1=CreateThread(NULL,0,ThreadFun,&thread1,0,0);//创建并开始线程

    if (!hThread1)

    {

               MessageBox("创建线程失败");

          }

    7.       编写线程函数(一般是一个死循环,或者需要花费时间很长的算法!否者就失去了多线程的意义)

    DWORD WINAPI ThreadFun(LPVOID pthread)         //线程入口函数

    {

    lpthread temp=(lpthread)pthread;//参数强制转换为结构体类型

    temp->progress->SetPos(temp->pos);    //设置被传递过来的进度条的位置

    while(temp->pos<20)

    {

               Sleep(temp->speed); /设置速度

               temp->pos++;  //增加进度

               temp->progress->SetPos(temp->pos);    //设置进度条的新位置

               GlobalVar--;

               if(temp->pos==20)

               {

                        temp->pos=0;                                                          //进度条满则归0

               }

    }

    return true;

    }

    8.       在挂起按钮函数中,编写如下代码:

    if(SuspendThread(hThread1)==0xFFFFFFFF)

    {

               MessageBox("挂起失败!进程可能已经死亡或未创建!");

               return;

            }

    9.       在执行按钮函数中,编写如下代码:

    if(ResumeThread(hThread1)==0xFFFFFFFF)

    {

               MessageBox("执行失败!进程可能已经死亡或未创建!");

               return;

            }

    10.   在停止按钮函数中,编写如下代码:

    if(TerminateThread(hThread1,0))//前些终止线程

    {

           CloseHandle(hThread1);//销毁线程句柄

    }

    else

    {

               MessageBox("终止进程失败!");

    }

    11.   为应用程序添加WM_TIMER消息,实时更新全局变量的值到编辑框;

    () MFC对多线程编程的支持

    1MFC线程的创建

            MFC中有两类线程,分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。

      

            工作者线程没有消息机制,通常用来执行后台计算和维护任务,如冗长的计算过程,打印机的后台打印等。用户界面线程一般用于处理独立于其他线程执行之外的用户输入,响应用户及系统所产生的事件和消息等。但对于Win32API编程而言,这两种线程是没有区别的,它们都只需线程的启动地址即可启动线程来执行任务。

     

         MFC中,一般用全局函数AfxBeginThread()来创建并初始化一个线程的运行,该函数有两种重载形式,分别用于创建工作者线程和用户界面线程。两种重载函数原型和参数分别说明如下:

    (1)     CWinThread*AfxBeginThread(AFX_THREADPROC pfnThreadProc,

    LPVOID pParam,

    nPriority=THREAD_PRIORITY_NORMAL,

    UINT nStackSize=0,

    DWORD dwCreateFlags=0,

    LPSECURITY_ATTRIBUTESlpSecurityAttrs=NULL);

    PfnThreadProc:指向工作者线程的执行函数的指针,线程函数原型必须声明如下:

     UINT ExecutingFunction(LPVOID pParam);

    请注意,ExecutingFunction()应返回一个UINT类型的值,用以指明该函数结束的原因。一般情况下,返回0表明执行成功。

    pParam传递给线程函数的一个32位参数,执行函数将用某种方式解释该值。它可以是数值,或是指向一个结构的指针,甚至可以被忽略;

    nPriority:线程的优先级。如果为0,则线程与其父线程具有相同的优先级;

    nStackSize:线程为自己分配堆栈的大小,其单位为字节。如果nStackSize被设为0,则线程的堆栈被设置成与父线程堆栈相同大小;

    dwCreateFlags:如果为0,则线程在创建后立刻开始执行。如果为CREATE_SUSPEND,则线程在创建后立刻被挂起;

    lpSecurityAttrs:线程的安全属性指针,一般为NULL

    (2)     CWinThread*AfxBeginThread(CRuntimeClass*pThreadClass,

    int nPriority=THREAD_PRIORITY_NORMAL,

    UINT nStackSize=0,

    DWORD dwCreateFlags=0,

    LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);

    pThreadClass是指向CWinThread的一个导出类的运行时类对象的指针,该导出类定义了被创建的用户界面线程的启动、退出等;其它参数的意义同形式1。使用函数的这个原型生成的线程也有消息机制,此消息机制同主线程的机制几乎一样。

      在工作线程中使用的函数指针一般是指向全局函数的而不是类成员函数,因为这牵扯到对象的生命周期,如果一个对象在线程执行时被销毁了,那么这个线程的行为就成为不确定的了。

    用户界面线程举例:

    源代码:http://download.csdn.net/detail/nuptboyzhb/4186916

    AfxBeginThread函数创建用户界面线程时,第一个参数不是一个函数,而是CWinThread派生对象的RUNTIME_CLASS

    步骤:

    1.       创建一个单文档的应用程序,添加一个菜单项,并为其添加消息响应函数;

    2.       添加新的线程类,并命名为CUIThread,其父类为CWinThread类;为CUIThread类重载其InitInstance函数,用于线程的初始化,重载其ExitInstance函数,用于释放线程占用的资源;

    3.       新建一个对话框资源(此对话框,也即是用户界面),命名为CUIDlg;资源IDIDD_DIALOG1

    4.       在CUIThreadInitInstance函数中编辑如下代码:

     CUIDlg *pDlg=new CUIDlg;

    pDlg->Create(IDD_DIALOG1);

    pDlg->ShowWindow(SW_SHOW);

    CRect RECT;

    pDlg->GetWindowRect(RECT);

    RECT.bottom+=100;

    RECT.left+=100;

    RECT.right+=100;

    RECT.top+=100;

        pDlg->MoveWindow(/*CRect(10,10,200,200)*/RECT,TRUE);

    5.       在菜单项的消息响应函数中,添加如下代码:

    CWinThread *pThread=AfxBeginThread(RUNTIME_CLASS(CUIThread),0,0,NULL);

    后续:在对话框中可以完成一系列的功能;本例子完成了在对话框中单击按钮,实现主程序窗口的显示和隐藏!当然,也可以实现其他功能;如利用自定义消息进行线程间通信等;

    用户界面的参数传递

     //创建线程,并挂起线程
     CUIThread *pThread=(CUIThread*)AfxBeginThread(RUNTIME_CLASS(CUIThread),THREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDED);
        //传递参数
     pThread->m_str="Hello!我是主线程传递过来的CString类型的变量!";
     //执行线程
     pThread->ResumeThread();

    2)线程间通讯

      一般而言,应用程序中的一个次要线程总是为主线程执行特定的任务,这样,主线程和次要线程间必定有一个信息传递的渠道,也就是主线程和次要线程间要进行通信。这种线程间的通信不但是难以避免的,而且在多线程编程中也是复杂和频繁的,下面将进行说明。

    a.使用全局变量进行通信

    由于属于同一个进程的各个线程共享操作系统分配该进程的资源,故解决线程间通信最简单的一种方法是使用全局变量。对于标准类型的全局变量,建议使用volatile修饰符,它告诉编译器无需对该变量作任何的优化,即无需将它放到一个寄存器中,并且该值可被外部改变。如果线程间所需传递的信息较复杂,我们可以定义一个结构,通过传递指向该结构的指针进行传递信息。

    b.使用自定义消息

    我们可以在一个线程的执行函数中向另一个线程发送自定义的消息来达到通信的目的。一个线程向另外一个线程发送消息是通过操作系统实现的。利用 Windows操作系统的消息驱动机制,当一个线程发出一条消息时,操作系统首先接收到该消息,然后把该消息转发给目标线程,接收消息的线程必须已经建立了消息循环。

    例如,我们想增加一个用户自定义消息WM_USER_THREADEND其方法是:

         1.  在头文件stdafx.h中增加一个自定义消息宏 

             #define WM_USER_THREADEND WM_USER + 1

         2.  在于增加新消息的窗口或对话框类的头文件中增加一个回调函数声明,注意要声明为public

             afx_msg LRESULT OnUserThreadend(WPARAM wParam, LPARAM lParam);

         3.  在窗口或对话框的cpp文件的BEGIN_MESSAGE_MAPEND_MESSAGE_MAP中增加一行         

             ON_MESSAGE(WM_USER_THREADEND, OnUserThreadend)

         4.  在窗口或对话框的cpp文件中增加回调函数的实现,如:

             LRESULT ThreadDialog::OnUserThreadend(WPARAM wParam, LPARAM lParam)

             {

               TRACE("WM_USER_THREADEND message /n");

                return 0;

             }

         5.  自定义消息的触发

             ::PostMessage(GetSafeHwnd(), WM_USER_THREADEND, 0, 0);

             其中GetSafeHwnd()得到了一个当前窗口的句柄,此消息将发给当前窗口,如果想发送消息给其它窗口只需改变这个句柄,前提是目的窗口也实现了此消息的处理函数。

    (3) 线程同步

      虽然多线程能给我们带来好处,但是也有不少问题需要解决。例如,对于像磁盘驱动器这样独占性系统资源,由于线程可以执行进程的任何代码段,且线程的运行是由系统调度自动完成的,具有一定的不确定性,因此就有可能出现两个线程同时对磁盘驱动器进行操作,从而出现操作错误;又例如,对于银行系统的计算机来说,可能使用一个线程来更新其用户数据库,而用另外一个线程来读取数据库以响应储户的需要,极有可能读数据库的线程读取的是未完全更新的数据库,因为可能在读的时候只有一部分数据被更新过

      使隶属于同一进程的各线程协调一致地工作称为线程的同步。MFC提供了多种同步对象,下面我们只介绍最常用的四种:

    临界区(CCriticalSection

    事件(CEvent

    互斥量(CMutex

    信号量(CSemaphore

     A、使用 CCriticalSection

      当多个线程访问一个独占性共享资源,可以使用“临界区”对象。任一时刻只有一个线程可以拥有临界区对象,拥有临界区的线程可以访问被保护起来的资源或代码段,其他希望进入临界区的线程将被挂起等待,直到拥有临界区的线程放弃临界区时为止,这样就保证了不会在同一时刻出现多个线程访问共享资源。

    CCriticalSection类的用法非常简单,步骤如下:

    定义CCriticalSection类的一个全局对象(以使各个线程均能访问),如CCriticalSection critical_section

    在访问需要保护的资源或代码之前,调用CCriticalSection类的成员Lock()获得临界区对象:

    critical_section.Lock();

    在线程中调用该函数来使线程获得它所请求的临界区。如果此时没有其它线程占有临界区对象,则调用Lock()的线程获得临界区;否则,线程将被挂起,并放入到一个系统队列中等待,直到当前拥有临界区的线程释放了临界区时为止。

    访问临界区完毕后,使用CCriticalSection的成员函数Unlock()来释放临界区:

    critical_section.Unlock();

    通俗一点讲,就是,调用 critical_section.Lock()进程,一直获得执行的权限,直到它调用critical_section.Unlock();为止!

    B、使用 CEvent

      CEvent 类提供了对事件的支持。事件是一个允许一个线程在某种情况发生时,唤醒另外一个线程的同步对象。例如在某些网络应用程序中,一个线程(记为A)负责监听通讯端口,另外一个线程(记为B)负责更新用户数据。通过使用CEvent类,线程A可以通知线程B何时更新用户数据。每一个CEvent对象可以有两种状态:有信号状态和无信号状态。线程监视位于其中的CEvent类对象的状态,并在相应的时候采取相应的操作。

      在MFC中,CEvent类对象有两种类型:人工事件和自动事件。一个自动CEvent对象在被至少一个线程释放后会自动返回到无信号状态;而人工事件对象获得信号后,释放可利用线程,但直到调用成员函数ReSetEvent()才将其设置为无信号状态。在创建CEvent类的对象时,默认创建的是自动事件。 CEvent类的各成员函数的原型和参数说明如下:

    1、  CEvent(BOOL bInitiallyOwn=FALSE,

    BOOL bManualReset=FALSE,

    LPCTSTR lpszName=NULL,

    LPSECURITY_ATTRIBUTES lpsaAttribute=NULL);

    bInitiallyOwn:指定事件对象初始化状态,TRUE为有信号,FALSE为无信号;

    bManualReset:指定要创建的事件是属于人工事件还是自动事件。TRUE为人工事件,FALSE为自动事件;

    后两个参数一般设为NULL,在此不作过多说明。

    2BOOL CEvent::SetEvent();

    CEvent 类对象的状态设置为有信号状态。如果事件是人工事件,则 CEvent 类对象保持为有信号状态,直到调用成员函数ResetEvent()将其重新设为无信号状态时为止。如果CEvent类对象为自动事件,则在SetEvent()将事件设置为有信号状态后,CEvent类对象由系统自动重置为无信号状态。

    如果该函数执行成功,则返回非零值,否则返回零。

    3BOOL CEvent::ResetEvent();

    该函数将事件的状态设置为无信号状态,并保持该状态直至SetEvent()被调用时为止。由于自动事件是由系统自动重置,故自动事件不需要调用该函数。如果该函数执行成功,返回非零值,否则返回零。我们一般通过调用WaitForSingleObject函数来监视事件状态。

    对于Event对象我们有两种实现方法,一个是CEvent,这是MFC提供给我们的,另外一个就是使用CreateEvent函数,此函数的定义如下:

    HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,

                      BOOL bManualReset,

                      BOOL bInitialState,

                      LPCTSTR lpName );

    此函数返回一个内核对象的句柄,在一般的情况下CEvent是第一选择,但是在我使用CEvent的过程中WaitForMultipleObjectsCEvent对象并不能很好的工作。

    举例:

    1.      声明一个全局的HANDLE wait_event;用于保存创建信号对象的句柄;

    2.      在对话框的初始化函数中,创建手动重置的事件对象;

    wait_event=CreateEvent(NULL,TRUE,FALSE,"nupt");//创建一个nupt标识的用户事件对象

    3.      在进程函数中,添加一句等待

    if (GlobalVar%10==0)

               {

                        AfxMessageBox("变量的值已经是10的倍数!进程等待...");

                        WaitForSingleObject(wait_event,INFINITE);

                        ResetEvent(wait_event);

               }

    也就是说,如果变量是10 的倍数,提示用户,线程进入WaitForSingleObject,如果wait_event有信号,则继续执行;如果wait_event无信号状态,进程将等待,等待时间可以设置;本例中设置的是无限等待;

    4.      添加两个按钮,编辑消息响应函数,一个是设置事件对象为有信号状态,一个是设置事件对象为无信号状态;

    源代码:

    C、使用CMutex

      互斥对象与临界区对象很像.互斥对象与临界区对象的不同在于:互斥对象可以在进程间使用,而临界区对象只能在同一进程的各线程间使用。当然,互斥对象也可以用于同一进程的各个线程间,但是在这种情况下,使用临界区会更节省系统资源,更有效率。

    :在一个应用程序的InitInstance()函数,创建一个CMutex对象,可以判断改程序是否已经运行:

    HANDLE hMutex;  //定义一个句柄

    hMutex = CreateMutex(NULL, TRUE, "APP程序"); //主线程拥有互斥对象

    if (hMutex)  //判断句柄是否有值

    {

    if (ERROR_ALREADY_EXISTS == GetLastError())  //判断程序是否已运行

    {

             AfxMessageBox("该应用程序已运行!  ", MB_ICONINFORMATION | MB_OK);

             ExitProcess(0);  //退出应用程序

    }

    }

    ReleaseMutex(hMutex);  //释放互斥对象

    D、使用CSemaphore

      当需要一个计数器来限制可以使用某个线程的数目时,可以使用“信号量”对象。CSemaphore类的对象保存了对当前访问某一指定资源的线程的计数值,该计数值是当前还可以使用该资源的线程的数目。如果这个计数达到了零,则所有对这个CSemaphore类对象所控制的资源的访问尝试都被放入到一个队列中等待,直到超时或计数值不为零时为止。一个线程被释放已访问了被保护的资源时,计数值减1;一个线程完成了对被控共享资源的访问时,计数值增1。这个被CSemaphore类对象所控制的资源可以同时接受访问的最大线程数在该对象的构建函数中指定。

    CSemaphore 类的构造函数原型及参数说明如下:

    CSemaphore(LONG lInitialCount=1,

    LONG lMaxCount=1,

    LPCTSTR pstrName=NULL,

    LPSECURITY_ATTRIBUTES lpsaAttributes=NULL);

    lInitialCount:信号量对象的初始计数值,即可访问线程数目的初始值;

    lMaxCount:信号量对象计数值的最大值,该参数决定了同一时刻可访问由信号量保护的资源的线程最大数目;

    后两个参数在同一进程中使用一般为NULL,不作过多讨论;

      在用CSemaphore 类的构造函数创建信号量对象时要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时,则说明当前占用资源的线程数已经达到了所允许的最大数目,不能再允许其它线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过 ReleaseSemaphore()函数将当前可用资源数加1

    头文件为:#include<afxmt.h>

    展开全文
  • Linux多线程编程()---多线程基本编程

    万次阅读 多人点赞 2013-07-20 10:58:59
    一个线程指的是进程中一个单一顺序的控制流,也被称为轻量级线程。它是系统独立调度和分配的基本单位。同一进程中的多个线程将共享该系统中的全部系统资源,比如文件描述符和信号处理等。一个进程可以有很多线程,每...

    线程概念

        线程是指运行中的程序的调度单位。一个线程指的是进程中一个单一顺序的控制流,也被称为轻量级线程。它是系统独立调度和分配的基本单位。同一进程中的多个线程将共享该系统中的全部系统资源,比如文件描述符和信号处理等。一个进程可以有很多线程,每个线程并行执行不同的任务。

    线程与进程比较

       ①  和进程相比,它是一种非常“节俭”的多任务操作方式。在Linux系统中,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护其代码段、堆栈段和数据段,这种多任务工作方式的代价非常“昂贵”。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且线程间彼此切换所需要时间也远远小于进程间切换所需要的时间。

       ②  线程间方便的通信机制。对不同进程来说它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行。这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其他线程所用,不仅方便,而且快捷。

    线程基本编程

       Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。因为pthread的库不是Linux系统的库,所以在编译时要加上 -lpthread。例如:gcc  filename  -lpthread。注意,这里要讲的线程相关操作都是用户空间中的线程的操作。

       线程创建:创建线程实际上就是确定调用该线程函数的入口点,这里通常使用的函数是pthread_create()。在线程创建后,就开始运行相关的线程函数。

      

      线程退出:在线程创建后,就开始运行相关的线程函数,在该函数运行完之后,该线程也就退出了,这也是线程退出的一种方法。另一种退出线程的方法是使用函数pthread_exit(),这是线程的主动行为。这里要注意的是,在使用线程函数时,不能随意使用exit()退出函数来进行出错处理。由于exit()的作用是使调用进程终止,而一个进程往往包含多个线程,因此,在使用exit()之后,该进程中的所有线程都终止了。在线程中就可以使用pthread_exit()来代替进程中的exit()。

      

      线程等待:由于一个进程中的多个线程是共享数据段的,因此,通常在线程退出后,退出线程所占用的资源并不会随着线程的终止而得到释放。正如进程之间可以用wait()系统调用来同步终止并释放资源一样,线程之间也有类似机制,那就是pthread_join()函数。pthread_join()用于将当前进程挂起来等待线程的结束。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源就被收回。

      

      线程取消:前面已经提到线程调用pthread_exit()函数主动终止自身线程,但是在很多线程应用中,经常会遇到在别的线程中要终止另一个线程的问题,此时调用pthread_cancel()函数来实现这种功能,但在被取消的线程的内部需要调用pthread_setcancel()函数和pthread_setcanceltype()函数设置自己的取消状态。例如,被取消的线程接收到另一个线程的取消请求之后,是接受函数忽略这个请求;如果是接受,则再判断立刻采取终止操作还是等待某个函数的调用等。

      

      线程标识符获取:获取调用线程的标识ID。

      

      线程清除:线程终止有两种情况:正常终止和非正常终止。线程主动调用pthread_exit()或者从线程函数中return都将使线程正常退出,这是可预见的退出方式;非正常终止是线程在其它线程的干预下,或者由于自身运行出错(比如访问非法地址)而退出,这种退出方式是不可预见的。不论是可预见的线程终止还是异常终止,都回存在资源释放的问题,如何保证线程终止时能顺利地释放掉自己所占用的资源,是一个必须考虑的问题。

      从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包括调用pthread_exit()和异常终止,不包括return)都将执行pthread_cleanup_push()所指定的清理函数。

      

       

    实验1

       功能:使用pthread_create()函数创建线程的实例

       代码:thread_create.c文件

        

         

        编译:使用命令:gcc thread_create.c -o thread_create -lpthread编译,注意不要忘了加 -lpthread,否则会出现如下的错误

    /tmp/ccjfZIN3.o: In function `main':
    thread_create.c:(.text+0x8b): undefined reference to `pthread_create'
    thread_create.c:(.text+0xc0): undefined reference to `pthread_create'
    thread_create.c:(.text+0xeb): undefined reference to `pthread_join'
    thread_create.c:(.text+0xfc): undefined reference to `pthread_join'
    collect2: ld returned 1 exit status

       执行:

       

    实验2

      功能:使用pthread_exit()函数退出线程的举例

      代码:thread_exit.c文件

         

      编译:gcc thread_exit.c -o thread_exit -lpthread

      执行:./thread_exit

      

    实验3

      功能:用pthread_join()实现线程等待。

      代码:thread_join.c文件

      

      编译:gcc thread_join.c -o thread_join -lpthread  

      执行:./thread_join

      

     可以看出,pthread_join()等到线程结束后,程序才继续执行。

    实验4

      功能:使用pthread_self()获取线程ID

      代码:thread_id.c文件

      

      编译:gcc thread_id.c -o thread_id -lpthread

      执行:./thread_id

      

    实验5

      功能:线程清理函数的使用

      代码:thread_clean.c

      

      

       

       注意,在编写的代码的时候,自己修改一下传递的参数和clean_pop函数的参数,相信你会更有收获。

      编译:gcc thread_clean.c -o thread_clean -lpthread

      执行:./thread_clean

       

    实验6

      功能:本实验创建了3个进程,为了更好的描述线程之间的并行执行,让3个线程共用同一个执行函数。每个线程都有5次循环(可以看成5个小任务),每次循环之间会随机等待1~10s的时间,意义在于模拟每个任务的到达时间是随机的,并没有任何特定的规律。

      代码:thread.c文件,该代码文件点此下载

      

      

      编译:gcc thread.c -o thread -lpthread

      执行:./thread

        

      从实验结果可以看出,线程执行的顺序杂乱无章的,看着就头疼,下一节就利用线程之间的同步与互斥机制处理此文件http://blog.csdn.net/mybelief321/article/details/9390707

     

     

    展开全文
  • 线程编程10例子

    万次阅读 2012-07-11 14:50:04
    十个例子清晰列举啦多线程编程的奥妙。  VC中多线程使用比较广泛而且实用,在网上看到的教程.感觉写的挺好. 一、问题的提出 编写一个耗时的单线程程序:  新建一个基于对话框的应用程序SingleThread,在主...
    留个纪念,不错的总结。十个例子清晰列举啦多线程编程的奥妙。 

    VC中多线程使用比较广泛而且实用,在网上看到的教程.感觉写的挺好.

    一、问题的提出
    编写一个耗时的单线程程序:
      新建一个基于对话框的应用程序SingleThread,在主对话框IDD_SINGLETHREAD_DIALOG添加一个按钮,ID为IDC_SLEEP_SIX_SECOND,标题为

    “延时6秒”,添加按钮的响应函数,代码如下:

    void CSingleThreadDlg::OnSleepSixSecond()
    {
    Sleep(6000); //延时6秒
    }
      编译并运行应用程序,单击“延时6秒”按钮,你就会发现在这6秒期间程序就象“死机”一样,不在响应其它消息。为了更好地处理这种

    耗时的操作,我们有必要学习——多线程编程。
    二、多线程概述
      进程和线程都是操作系统的概念。进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它各种系统资源组成

    ,进程在运行过程中创建的资源随着进程的终止而被销毁,所使用的系统资源在进程终止时被释放或关闭。
      线程是进程内部的一个执行单元。系统创建好进程后,实际上就启动执行了该进程的主执行线程,主执行线程以函数地址形式,比如说

    main或WinMain函数,将程序的启动点提供给Windows系统。主执行线程终止了,进程也就随之终止。
      每一个进程至少有一个主执行线程,它无需由用户去主动创建,是由系统自动创建的。用户根据需要在应用程序中创建其它线程,多个线

    程并发地运行于同一个进程中。一个进程中的所有线程都在该进程的虚拟地址空间中,共同使用这些虚拟地址空间、全局变量和系统资源,所

    以线程间的通讯非常方便,多线程技术的应用也较为广泛。
      多线程可以实现并行处理,避免了某项任务长时间占用CPU时间。要说明的一点是,目前大多数的计算机都是单处理器(CPU)的,为了运

    行所有这些线程,操作系统为每个独立线程安排一些CPU时间,操作系统以轮换方式向线程提供时间片,这就给人一种假象,好象这些线程都在

    同时运行。由此可见,如果两个非常活跃的线程为了抢夺对CPU的控制权,在线程切换时会消耗很多的CPU资源,反而会降低系统的性能。这一

    点在多线程编程时应该注意。
      Win32 SDK函数支持进行多线程的程序设计,并提供了操作系统原理中的各种同步、互斥和临界区等操作。Visual C++
    6.0中,使用MFC类库也实现了多线程的程序设计,使得多线程编程更加方便。
    三、Win32 API对多线程编程的支持
      Win32 提供了一系列的API函数来完成线程的创建、挂起、恢复、终结以及通信等工作。下面将选取其中的一些重要函数进行说明。
    1、HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,
                     DWORD dwStackSize,
                     LPTHREAD_START_ROUTINE lpStartAddress,
                     LPVOID lpParameter,
                     DWORD dwCreationFlags,
                     LPDWORD lpThreadId);
    该函数在其调用进程的进程空间里创建一个新的线程,并返回已建线程的句柄,其中各参数说明如下:
    lpThreadAttributes:指向一个 SECURITY_ATTRIBUTES 结构的指针,该结构决定了线程的安全属性,一般置为 NULL;
    dwStackSize:指定了线程的堆栈深度,一般都设置为0;
    lpStartAddress:表示新线程开始执行时代码所在函数的地址,即线程的起始地址。一般情况为(LPTHREAD_START_ROUTINE)ThreadFunc,

    ThreadFunc
    是线程函数名;
    lpParameter:指定了线程执行时传送给线程的32位参数,即线程函数的参数;
    dwCreationFlags:控制线程创建的附加标志,可以取两种值。如果该参数为0,线程在被创建后就会立即开始执行;如果该参数为

    CREATE_SUSPENDED,则系统产生线程后,该线程处于挂起状态,并不马上执行,直至函数ResumeThread被调用;

    lpThreadId:该参数返回所创建线程的ID;
    如果创建成功则返回线程的句柄,否则返回NULL。
    2、DWORD SuspendThread(HANDLE hThread);
    该函数用于挂起指定的线程,如果函数执行成功,则线程的执行被终止。 3、DWORD ResumeThread(HANDLE hThread);
    该函数用于结束线程的挂起状态,执行线程。 4、VOID ExitThread(DWORD dwExitCode);
    该函数用于线程终结自身的执行,主要在线程的执行函数中被调用。其中参数dwExitCode用来设置线程的退出码。 5、BOOL
    TerminateThread(HANDLE hThread,DWORD dwExitCode);
      一般情况下,线程运行结束之后,线程函数正常返回,但是应用程序可以调用TerminateThread强行终止某一线程的执行。各参数含义如下

    hThread:将被终结的线程的句柄;
    dwExitCode:用于指定线程的退出码。
      使用TerminateThread()终止某个线程的执行是不安全的,可能会引起系统不稳定;虽然该函数立即终止线程的执行,但并不释放线程所占

    用的资源。因此,一般不建议使用该函数。

    6、BOOL PostThreadMessage(DWORD idThread,
       UINT Msg,
       WPARAM wParam,
       LPARAM lParam);
    该函数将一条消息放入到指定线程的消息队列中,并且不等到消息被该线程处理时便返回。
    idThread:将接收消息的线程的ID;
    Msg:指定用来发送的消息;
    wParam:同消息有关的字参数;
    lParam:同消息有关的长参数;
    调用该函数时,如果即将接收消息的线程没有创建消息循环,则该函数执行失败。
    四、Win32 API多线程编程例程
    例程1 MultiThread1
    建立一个基于对话框的工程MultiThread1,在对话框IDD_MULTITHREAD1_DIALOG中加入两个按钮和一个编辑框,两个按钮的ID分别是IDC_START

    ,IDC_STOP
    ,标题分别为“启动”,“停止”,IDC_STOP的属性选中Disabled;编辑框的ID为IDC_TIME ,属性选中Read-only;
     
    在MultiThread1Dlg.h文件中添加线程函数声明: void ThreadFunc();
    注意,线程函数的声明应在类CMultiThread1Dlg的外部。在类CMultiThread1Dlg内部添加protected型变量: HANDLE
    hThread;
    DWORD ThreadID;
    分别代表线程的句柄和ID。
     
    在MultiThread1Dlg.cpp文件中添加全局变量m_bRun : volatile BOOL m_bRun;
    m_bRun 代表线程是否正在运行。
    你要留意到全局变量 m_bRun 是使用 volatile 修饰符的,volatile
    修饰符的作用是告诉编译器无需对该变量作任何的优化,即无需将它放到一个寄存器中,并且该值可被外部改变。对于多线程引用的全局变量

    来说,volatile
    是一个非常重要的修饰符。
    编写线程函数: void ThreadFunc()
    {
    CTime time;
    CString strTime;
    m_bRun=TRUE;
    while(m_bRun)
    {
    time=CTime::GetCurrentTime();
    strTime=time.Format("%H:%M:%S");
    ::SetDlgItemText(AfxGetMainWnd()->m_hWnd,IDC_TIME,strTime);
    Sleep(1000);
    }
    }
    该线程函数没有参数,也不返回函数值。只要m_bRun为TRUE,线程一直运行。
    双击IDC_START按钮,完成该按钮的消息函数: void CMultiThread1Dlg::OnStart()
    {
    // TODO: Add your control notification handler code here
    hThread=CreateThread(NULL,
    0,
    (LPTHREAD_START_ROUTINE)ThreadFunc,
    NULL,
    0,
    &ThreadID);
    GetDlgItem(IDC_START)->EnableWindow(FALSE);
    GetDlgItem(IDC_STOP)->EnableWindow(TRUE);
    }
    双击IDC_STOP按钮,完成该按钮的消息函数: void CMultiThread1Dlg::OnStop()
    {
    // TODO: Add your control notification handler code here
    m_bRun=FALSE;
    GetDlgItem(IDC_START)->EnableWindow(TRUE);
    GetDlgItem(IDC_STOP)->EnableWindow(FALSE);
    }
    编译并运行该例程,体会使用Win32 API编写的多线程。

    例程2 MultiThread2
      该线程演示了如何传送一个一个整型的参数到一个线程中,以及如何等待一个线程完成处理。
    建立一个基于对话框的工程MultiThread2,在对话框IDD_MULTITHREAD2_DIALOG中加入一个编辑框和一个按钮,ID分别是IDC_COUNT,IDC_START
    ,按钮控件的标题为“开始”;
    在MultiThread2Dlg.h文件中添加线程函数声明: void ThreadFunc(int integer);
    注意,线程函数的声明应在类CMultiThread2Dlg的外部。
    在类CMultiThread2Dlg内部添加protected型变量: HANDLE hThread;
    DWORD ThreadID;
    分别代表线程的句柄和ID。
     
    打开ClassWizard,为编辑框IDC_COUNT添加int型变量m_nCount。在MultiThread2Dlg.cpp文件中添加:void
    ThreadFunc(int integer)
    {
    int i;
    for(i=0;i<integer;i++)
    {
    Beep(200,50);
    Sleep(1000);
    }
    }
    双击IDC_START按钮,完成该按钮的消息函数: void CMultiThread2Dlg::OnStart()
    {
    UpdateData(TRUE);
    int integer=m_nCount;
    hThread=CreateThread(NULL,
    0,
    (LPTHREAD_START_ROUTINE)ThreadFunc,
    (VOID*)integer,
    0,
    &ThreadID);
    GetDlgItem(IDC_START)->EnableWindow(FALSE);
    WaitForSingleObject(hThread,INFINITE);
    GetDlgItem(IDC_START)->EnableWindow(TRUE);
    }
    顺便说一下WaitForSingleObject函数,其函数原型为:DWORD WaitForSingleObject(HANDLE hHandle,DWORD
    dwMilliseconds);
    hHandle为要监视的对象(一般为同步对象,也可以是线程)的句柄;
    dwMilliseconds为hHandle对象所设置的超时值,单位为毫秒;
      当在某一线程中调用该函数时,线程暂时挂起,系统监视hHandle所指向的对象的状态。如果在挂起的dwMilliseconds毫秒内,线程所等待

    的对象变为有信号状态,则该函数立即返回;如果超时时间已经到达dwMilliseconds毫秒,但hHandle所指向的对象还没有变成有信号状态,函

    数照样返回。参数dwMilliseconds有两个具有特殊意义的值:0和INFINITE。若为0,则该函数立即返回;若为INFINITE,则线程一直被挂起,

    直到hHandle所指向的对象变为有信号状态时为止。
      本例程调用该函数的作用是按下IDC_START按钮后,一直等到线程返回,再恢复IDC_START按钮正常状态。编译运行该例程并细心体会。
    例程3 MultiThread3
    传送一个结构体给一个线程函数也是可能的,可以通过传送一个指向结构体的指针参数来完成。先定义一个结构体:
    typedef struct
    {
    int firstArgu,
    long secondArgu,
    }myType,*pMyType;
    创建线程时CreateThread(NULL,0,threadFunc,pMyType,…);
    在threadFunc函数内部,可以使用“强制转换”:
    int intValue=((pMyType)lpvoid)->firstArgu;
    long longValue=((pMyType)lpvoid)->seconddArgu;
    ……
    例程3 MultiThread3将演示如何传送一个指向结构体的指针参数。
    建立一个基于对话框的工程MultiThread3,在对话框IDD_MULTITHREAD3_DIALOG中加入一个编辑框IDC_MILLISECOND,一个按钮IDC_START,标题

    为“开始”
    ,一个进度条IDC_PROGRESS1;
    打开ClassWizard,为编辑框IDC_MILLISECOND添加int型变量m_nMilliSecond,为进度条IDC_PROGRESS1添加CProgressCtrl型变量

    m_ctrlProgress;

    在MultiThread3Dlg.h文件中添加一个结构的定义: struct threadInfo
    {
    UINT nMilliSecond;
    CProgressCtrl* pctrlProgress;
    };
    线程函数的声明: UINT ThreadFunc(LPVOID lpParam);
    注意,二者应在类CMultiThread3Dlg的外部。
    在类CMultiThread3Dlg内部添加protected型变量: HANDLE hThread;
    DWORD ThreadID;
    分别代表线程的句柄和ID。
    在MultiThread3Dlg.cpp文件中进行如下操作:
    定义公共变量 threadInfo Info;
    双击按钮IDC_START,添加相应消息处理函数:void CMultiThread3Dlg::OnStart()
    {
    // TODO: Add your control notification handler code here
    UpdateData(TRUE);
    Info.nMilliSecond=m_nMilliSecond;
    Info.pctrlProgress=&m_ctrlProgress;
    hThread=CreateThread(NULL,
    0,
    (LPTHREAD_START_ROUTINE)ThreadFunc,
    &Info,
    0,
    &ThreadID);

    }
    在函数BOOL CMultiThread3Dlg::OnInitDialog()中添加语句: {
    ……

    // TODO: Add extra initialization here
    m_ctrlProgress.SetRange(0,99);
    m_nMilliSecond=10;
    UpdateData(FALSE);
    return TRUE; // return TRUE unless you set the focus to a control
    }
    添加线程处理函数:UINT ThreadFunc(LPVOID lpParam) {
    threadInfo* pInfo=(threadInfo*)lpParam;
    for(int i=0;i<100;i++)
    {
    int nTemp=pInfo->nMilliSecond;
    pInfo->pctrlProgress->SetPos(i);
    Sleep(nTemp);
    }
    return 0;
    }
      顺便补充一点,如果你在void CMultiThread3Dlg::OnStart() 函数中添加语句,编译运行你就会发现进度条不进行刷新,主线程也停止了反应。什么原因呢?这是因为WaitForSingleObject函数等待子线程

    (ThreadFunc)结束时,导致了线程死锁。因为WaitForSingleObject函数会将主线程挂起(任何消息都得不到处理),而子线程ThreadFunc正

    在设置进度条,一直在等待主线程将刷新消息处理完毕返回才会检测通知事件。这样两个线程都在互相等待,死锁发生了,编程时应注意避免


    例程4 MultiThread4
    该例程测试在Windows下最多可创建线程的数目。

    建立一个基于对话框的工程MultiThread4,在对话框IDD_MULTITHREAD4_DIALOG中加入一个按钮IDC_TEST和一个编辑框IDC_COUNT,按钮标题为

    “测试”
    , 编辑框属性选中Read-only;
    在MultiThread4Dlg.cpp文件中进行如下操作:
    添加公共变量volatile BOOL m_bRunFlag=TRUE;
    该变量表示是否还能继续创建线程。
    添加线程函数:
    DWORD WINAPI threadFunc(LPVOID threadNum)
    {
    while(m_bRunFlag)
    {
    Sleep(3000);
    }
    return 0;
    }
    只要 m_bRunFlag 变量为TRUE,线程一直运行。
    双击按钮IDC_TEST,添加其响应消息函数:void CMultiThread4Dlg::OnTest()
    {
    DWORD threadID;
    GetDlgItem(IDC_TEST)->EnableWindow(FALSE);
    long nCount=0;
    while(m_bRunFlag)
    {
    if(CreateThread(NULL,0,threadFunc,NULL,0,&threadID)==NULL)
    {
       m_bRunFlag=FALSE;
       break;
    }
    else
    {
       nCount++;
    }
    }
       //不断创建线程,直到再不能创建为止
    m_nCount=nCount;
    UpdateData(FALSE);
    Sleep(5000);
       //延时5秒,等待所有创建的线程结束
    GetDlgItem(IDC_TEST)->EnableWindow(TRUE);
        m_bRunFlag=TRUE;
    }
    五、MFC对多线程编程的支持
      MFC中有两类线程,分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息

    队列和消息循环。
      工作者线程没有消息机制,通常用来执行后台计算和维护任务,如冗长的计算过程,打印机的后台打印等。用户界面线程一般用于处理独

    立于其他线程执行之外的用户输入,响应用户及系统所产生的事件和消息等。但对于Win32的API编程而言,这两种线程是没有区别的,它们都

    只需线程的启动地址即可启动线程来执行任务。
      在MFC中,一般用全局函数AfxBeginThread()来创建并初始化一个线程的运行,该函数有两种重载形式,分别用于创建工作者线程和用户界

    面线程。两种重载函数原型和参数分别说明如下:

    (1) CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc,
                          LPVOID pParam,
                          nPriority=THREAD_PRIORITY_NORMAL,
                          UINT nStackSize=0,
                          DWORD dwCreateFlags=0,
                          LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);
    PfnThreadProc:指向工作者线程的执行函数的指针,线程函数原型必须声明如下: UINT ExecutingFunction(LPVOID
    pParam);
    请注意,ExecutingFunction()应返回一个UINT类型的值,用以指明该函数结束的原因。一般情况下,返回0表明执行成功。
    pParam:传递给线程函数的一个32位参数,执行函数将用某种方式解释该值。它可以是数值,或是指向一个结构的指针,甚至可以被忽略;
    nPriority:线程的优先级。如果为0,则线程与其父线程具有相同的优先级;
    nStackSize:线程为自己分配堆栈的大小,其单位为字节。如果nStackSize被设为0,则线程的堆栈被设置成与父线程堆栈相同大小;
    dwCreateFlags:如果为0,则线程在创建后立刻开始执行。如果为CREATE_SUSPEND,则线程在创建后立刻被挂起;
    lpSecurityAttrs:线程的安全属性指针,一般为NULL;
    (2) CWinThread* AfxBeginThread(CRuntimeClass* pThreadClass,
                          int nPriority=THREAD_PRIORITY_NORMAL,
                          UINT nStackSize=0,
                          DWORD dwCreateFlags=0,
                          LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);

      pThreadClass 是指向 CWinThread
    的一个导出类的运行时类对象的指针,该导出类定义了被创建的用户界面线程的启动、退出等;其它参数的意义同形式1。使用函数的这个原型

    生成的线程也有消息机制,在以后的例子中我们将发现同主线程的机制几乎一样。
    下面我们对CWinThread类的数据成员及常用函数进行简要说明。
    m_hThread:当前线程的句柄;
    m_nThreadID:当前线程的ID;
    m_pMainWnd:指向应用程序主窗口的指针
    BOOL CWinThread::CreateThread(DWORD dwCreateFlags=0,
    UINT nStackSize=0,
    LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);
      该函数中的dwCreateFlags、nStackSize、lpSecurityAttrs参数和API函数CreateThread中的对应参数有相同含义,该函数执行成功,返回

    非0值,否则返回0。
      一般情况下,调用AfxBeginThread()来一次性地创建并启动一个线程,但是也可以通过两步法来创建线程:首先创建CWinThread类的一个

    对象,然后调用该对象的成员函数CreateThread()来启动该线程。

    virtual BOOL CWinThread::InitInstance();
      重载该函数以控制用户界面线程实例的初始化。初始化成功则返回非0值,否则返回0。用户界面线程经常重载该函数,工作者线程一般不

    使用InitInstance()。
    virtual int CWinThread::ExitInstance();
      在线程终结前重载该函数进行一些必要的清理工作。该函数返回线程的退出码,0表示执行成功,非0值用来标识各种错误。同

    InitInstance()成员函数一样,该函数也只适用于用户界面线程。
     
    六、MFC多线程编程实例
      在Visual C++
    6.0编程环境中,我们既可以编写C风格的32位Win32应用程序,也可以利用MFC类库编写C++风格的应用程序,二者各有其优缺点。基于Win32的

    应用程序执行代码小巧,运行效率高,但要求程序员编写的代码较多,且需要管理系统提供给程序的所有资源;而基于MFC类库的应用程序可以

    快速建立起应用程序,类库为程序员提供了大量的封装类,而且Developer
    Studio为程序员提供了一些工具来管理用户源程序,其缺点是类库代码很庞大。由于使用类库所带来的快速、简捷和功能强大等优越性,因此

    除非有特殊的需要,否则Visual
    C++推荐使用MFC类库进行程序开发。
    我们知道,MFC中的线程分为两种:用户界面线程和工作者线程。我们将分别举例说明。
    用 MFC 类库编程实现工作者线程
    例程5 MultiThread5
    为了与Win32 API对照,我们使用MFC 类库编程实现例程3 MultiThread3。
    建立一个基于对话框的工程MultiThread5,在对话框IDD_MULTITHREAD5_DIALOG中加入一个编辑框IDC_MILLISECOND,一个按钮IDC_START,标题

    为“开始”
    ,一个进度条IDC_PROGRESS1;
    打开ClassWizard,为编辑框IDC_MILLISECOND添加int型变量m_nMilliSecond,为进度条IDC_PROGRESS1添加CProgressCtrl型变量

    m_ctrlProgress;

    在MultiThread5Dlg.h文件中添加一个结构的定义: struct threadInfo
    {
    UINT nMilliSecond;
    CProgressCtrl* pctrlProgress;
    };
    线程函数的声明:UINT ThreadFunc(LPVOID lpParam);
    注意,二者应在类CMultiThread5Dlg的外部。
    在类CMultiThread5Dlg内部添加protected型变量:
    CWinThread* pThread;
    在MultiThread5Dlg.cpp文件中进行如下操作:定义公共变量:threadInfo Info;
    双击按钮IDC_START,添加相应消息处理函数:
    void CMultiThread5Dlg::OnStart()
    {
    // TODO: Add your control notification handler code here
    UpdateData(TRUE);
    Info.nMilliSecond=m_nMilliSecond;
    Info.pctrlProgress=&m_ctrlProgress;
    pThread=AfxBeginThread(ThreadFunc,
    &Info);
    }
    在函数BOOL CMultiThread3Dlg::OnInitDialog()中添加语句: {
    ……

    // TODO: Add extra initialization here
    m_ctrlProgress.SetRange(0,99);
    m_nMilliSecond=10;
    UpdateData(FALSE);
    return TRUE; // return TRUE unless you set the focus to a control
    }
    添加线程处理函数: UINT ThreadFunc(LPVOID lpParam)
    {
    threadInfo* pInfo=(threadInfo*)lpParam;
    for(int i=0;i<100;i++)
    {
    int nTemp=pInfo->nMilliSecond;
    pInfo->pctrlProgress->SetPos(i);
    Sleep(nTemp);
    }
    return 0;
    }
    用 MFC 类库编程实现用户界面线程
    创建用户界面线程的步骤:
    使用ClassWizard创建类CWinThread的派生类(以CUIThread类为例) class CUIThread : public
    CWinThread
    {
    DECLARE_DYNCREATE(CUIThread)
    protected:
    CUIThread();           // protected constructor used by dynamic creation
    // Attributes
    public:
    // Operations
    public:
    // Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CUIThread)
    public:
    virtual BOOL InitInstance();
    virtual int ExitInstance();
    //}}AFX_VIRTUAL
    // Implementation
    protected:
    virtual ~CUIThread();
    // Generated message map functions
    //{{AFX_MSG(CUIThread)
    // NOTE - the ClassWizard will add and remove member functions here.
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
    };
    重载函数InitInstance()和ExitInstance()。 BOOL CUIThread::InitInstance()
    {
    CFrameWnd* wnd=new CFrameWnd;
    wnd->Create(NULL,"UI Thread Window");
    wnd->ShowWindow(SW_SHOW);
    wnd->UpdateWindow();
    m_pMainWnd=wnd;
    return TRUE;
    }
    创建新的用户界面线程 void CUIThreadDlg::OnButton1()
    {
    CUIThread* pThread=new CUIThread();
    pThread->CreateThread();
    }
    请注意以下两点:
    A、在UIThreadDlg.cpp的开头加入语句: #include "UIThread.h"
    B、把UIThread.h中类CUIThread()的构造函数的特性由 protected 改为 public。
      用户界面线程的执行次序与应用程序主线程相同,首先调用用户界面线程类的InitInstance()函数,如果返回TRUE,继续调用线程的Run()

    函数,该函数的作用是运行一个标准的消息循环,并且当收到WM_QUIT消息后中断,在消息循环过程中,Run()函数检测到线程空闲时(没有消

    息),也将调用OnIdle()函数,最后Run()函数返回,MFC调用ExitInstance()函数清理资源。
      你可以创建一个没有界面而有消息循环的线程,例如:你可以从CWinThread派生一个新类,在InitInstance函数中完成某项任务并返回

    FALSE,这表示仅执行InitInstance函数中的任务而不执行消息循环,你可以通过这种方法,完成一个工作者线程的功能。

    例程6 MultiThread6
    建立一个基于对话框的工程MultiThread6,在对话框IDD_MULTITHREAD6_DIALOG中加入一个按钮IDC_UI_THREAD,标题为“用户界面线程”

    右击工程并选中“New Class…”为工程添加基类为CWinThread派生线程类CUIThread。
    给工程添加新对话框IDD_UITHREADDLG,标题为“线程对话框”。
    为对话框IDD_UITHREADDLG创建一个基于CDialog的类CUIThreadDlg。使用ClassWizard为CUIThreadDlg类添加WM_LBUTTONDOWN消息的处理函数

    OnLButtonDown,如下:
    void CUIThreadDlg::OnLButtonDown(UINT nFlags, CPoint point)
    {
    AfxMessageBox("You Clicked The Left Button!");
    CDialog::OnLButtonDown(nFlags, point);
    }
    在UIThread.h中添加 #include "UIThreadDlg.h"
    并在CUIThread类中添加protected变量CUIThread m_dlg: class CUIThread : public CWinThread
    {
    DECLARE_DYNCREATE(CUIThread)
    protected:
    CUIThread();           // protected constructor used by dynamic creation
    // Attributes
    public:
    // Operations
    public:
    // Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CUIThread)
    public:
    virtual BOOL InitInstance();
    virtual int ExitInstance();
    //}}AFX_VIRTUAL
    // Implementation
    protected:
    CUIThreadDlg m_dlg;
    virtual ~CUIThread();
    // Generated message map functions
    //{{AFX_MSG(CUIThread)
    // NOTE - the ClassWizard will add and remove member functions here.
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
    };
    分别重载InitInstance()函数和ExitInstance()函数: BOOL CUIThread::InitInstance()
    {
    m_dlg.Create(IDD_UITHREADDLG);
    m_dlg.ShowWindow(SW_SHOW);
    m_pMainWnd=&m_dlg;
    return TRUE;
    }
    int CUIThread::ExitInstance()
    {
    m_dlg.DestroyWindow();
    return CWinThread::ExitInstance();
    }
    双击按钮IDC_UI_THREAD,添加消息响应函数: void CMultiThread6Dlg::OnUiThread()
    {
    CWinThread *pThread=AfxBeginThread(RUNTIME_CLASS(CUIThread));
    }
    并在MultiThread6Dlg.cpp的开头添加: #include "UIThread.h"
      好了,编译并运行程序吧。每单击一次“用户界面线程”按钮,都会弹出一个线程对话框,在任何一个线程对话框内按下鼠标左键,都会

    弹出一个消息框。
    七、线程间通讯
      一般而言,应用程序中的一个次要线程总是为主线程执行特定的任务,这样,主线程和次要线程间必定有一个信息传递的渠道,也就是主线程

    和次要线程间要进行通信。这种线程间的通信不但是难以避免的,而且在多线程编程中也是复杂和频繁的,下面将进行说明。

    使用全局变量进行通信
    由于属于同一个进程的各个线程共享操作系统分配该进程的资源,故解决线程间通信最简单的一种方法是使用全局变量。对于标准类型的全局

    变量,我们建议使用volatile
    修饰符,它告诉编译器无需对该变量作任何的优化,即无需将它放到一个寄存器中,并且该值可被外部改变。如果线程间所需传递的信息较复

    杂,我们可以定义一个结构,通过传递指向该结构的指针进行传递信息。
     
    使用自定义消息
    我们可以在一个线程的执行函数中向另一个线程发送自定义的消息来达到通信的目的。一个线程向另外一个线程发送消息是通过操作系统实现

    的。利用Windows操作系统的消息驱动机制,当一个线程发出一条消息时,操作系统首先接收到该消息,然后把该消息转发给目标线程,接收消

    息的线程必须已经建立了消息循环。

    例程7 MultiThread7
      该例程演示了如何使用自定义消息进行线程间通信。首先,主线程向CCalculateThread线程发送消息WM_CALCULATE,CCalculateThread线

    程收到消息后进行计算,再向主线程发送WM_DISPLAY消息,主线程收到该消息后显示计算结果。

    建立一个基于对话框的工程MultiThread7,在对话框IDD_MULTITHREAD7_DIALOG中加入三个单选按钮IDC_RADIO1,IDC_RADIO2,IDC_RADIO3,标

    题分别为1+2+3+4+......+10,1+2+3+4+......+50,1+2+3+4+......+100。加入按钮IDC_SUM,标题为“求和”。加入标签框IDC_STATUS,属性

    选中“边框”;

    在MultiThread7Dlg.h中定义如下变量: protected:
    int nAddend;
    代表加数的大小。
    分别双击三个单选按钮,添加消息响应函数:void CMultiThread7Dlg::OnRadio1()
    {
    nAddend=10;
    }
    void CMultiThread7Dlg::OnRadio2()
    {
    nAddend=50;

    }
    void CMultiThread7Dlg::OnRadio3()
    {
    nAddend=100;

    }
    并在OnInitDialog函数中完成相应的初始化工作: BOOL CMultiThread7Dlg::OnInitDialog()
    {
    ……
    ((CButton*)GetDlgItem(IDC_RADIO1))->SetCheck(TRUE);
    nAddend=10;
    ……
    在MultiThread7Dlg.h中添加: #include "CalculateThread.h"
    #define WM_DISPLAY WM_USER+2
    class CMultiThread7Dlg : public CDialog
    {
    // Construction
    public:
    CMultiThread7Dlg(CWnd* pParent = NULL); // standard constructor
    CCalculateThread* m_pCalculateThread;
    ……
    protected:
    int nAddend;
    LRESULT OnDisplay(WPARAM wParam,LPARAM lParam);
    ……
    在MultiThread7Dlg.cpp中添加: BEGIN_MESSAGE_MAP(CMultiThread7Dlg, CDialog)
    ……
    ON_MESSAGE(WM_DISPLAY,OnDisplay)
    END_MESSAGE_MAP()
    LRESULT CMultiThread7Dlg::OnDisplay(WPARAM wParam,LPARAM lParam)
    {
    int nTemp=(int)wParam;
    SetDlgItemInt(IDC_STATUS,nTemp,FALSE);
    return 0;
    }
    以上代码使得主线程类CMultiThread7Dlg可以处理WM_DISPLAY消息,即在IDC_STATUS标签框中显示计算结果。
    双击按钮IDC_SUM,添加消息响应函数: void CMultiThread7Dlg::OnSum()
    {
    m_pCalculateThread=
    (CCalculateThread*)AfxBeginThread(RUNTIME_CLASS(CCalculateThread));
    Sleep(500);
    m_pCalculateThread->PostThreadMessage(WM_CALCULATE,nAddend,NULL);
    }
    OnSum()函数的作用是建立CalculateThread线程,延时给该线程发送WM_CALCULATE消息。
    右击工程并选中“New Class…”为工程添加基类为 CWinThread 派生线程类 CCalculateThread。
    在文件CalculateThread.h 中添加 #define WM_CALCULATE WM_USER+1
    class CCalculateThread : public CWinThread
    {
    ……
    protected:
    afx_msg LONG OnCalculate(UINT wParam,LONG lParam);
    ……
    在文件CalculateThread.cpp中添加 LONG CCalculateThread::OnCalculate(UINT wParam,LONG
    lParam)
    {
    int nTmpt=0;
    for(int i=0;i<=(int)wParam;i++)
    {
    nTmpt=nTmpt+i;
    }
    Sleep(500);
        ::PostMessage((HWND)(GetMainWnd()->GetSafeHwnd()),WM_DISPLAY,nTmpt,NULL);
    return 0;
    }
    BEGIN_MESSAGE_MAP(CCalculateThread, CWinThread)
    //{{AFX_MSG_MAP(CCalculateThread)
    // NOTE - the ClassWizard will add and remove mapping macros here.
    //}}AFX_MSG_MAP
    ON_THREAD_MESSAGE(WM_CALCULATE,OnCalculate)
    //和主线程对比,注意它们的区别
    END_MESSAGE_MAP()
    在CalculateThread.cpp文件的开头添加一条: #include "MultiThread7Dlg.h"
      以上代码为 CCalculateThread 类添加了 WM_CALCULATE 消息,消息的响应函数是 OnCalculate,其功能是根据参数
    wParam 的值,进行累加,累加结果在临时变量nTmpt中,延时0.5秒,向主线程发送WM_DISPLAY消息进行显示,nTmpt作为参数传递。
    编译并运行该例程,体会如何在线程间传递消息。
    八、线程的同步
      虽然多线程能给我们带来好处,但是也有不少问题需要解决。例如,对于像磁盘驱动器这样独占性系统资源,由于线程可以执行进程的任

    何代码段,且线程的运行是由系统调度自动完成的,具有一定的不确定性,因此就有可能出现两个线程同时对磁盘驱动器进行操作,从而出现

    操作错误;又例如,对于银行系统的计算机来说,可能使用一个线程来更新其用户数据库,而用另外一个线程来读取数据库以响应储户的需要

    ,极有可能读数据库的线程读取的是未完全更新的数据库,因为可能在读的时候只有一部分数据被更新过。
      使隶属于同一进程的各线程协调一致地工作称为线程的同步。MFC提供了多种同步对象,下面我们只介绍最常用的四种:
    临界区(CCriticalSection)
    事件(CEvent)
    互斥量(CMutex)
    信号量(CSemaphore)
     
    通过这些类,我们可以比较容易地做到线程同步。
    A、使用 CCriticalSection 类
      当多个线程访问一个独占性共享资源时,可以使用“临界区”对象。任一时刻只有一个线程可以拥有临界区对象,拥有临界区的线程可以访

    问被保护起来的资源或代码段,其他希望进入临界区的线程将被挂起等待,直到拥有临界区的线程放弃临界区时为止,这样就保证了不会在同

    一时刻出现多个线程访问共享资源。
    CCriticalSection类的用法非常简单,步骤如下:
     
    定义CCriticalSection类的一个全局对象(以使各个线程均能访问),如CCriticalSection critical_section;
    在访问需要保护的资源或代码之前,调用CCriticalSection类的成员Lock()获得临界区对象: critical_section.Lock();
    在线程中调用该函数来使线程获得它所请求的临界区。如果此时没有其它线程占有临界区对象,则调用Lock()的线程获得临界区;否则,线程

    将被挂起,并放入到一个系统队列中等待,直到当前拥有临界区的线程释放了临界区时为止。

    访问临界区完毕后,使用CCriticalSection的成员函数Unlock()来释放临界区:critical_section.Unlock();
    再通俗一点讲,就是线程A执行到critical_section.Lock();语句时,如果其它线程(B)正在执行critical_section.Lock();语句后且

    critical_section.
    Unlock();语句前的语句时,线程A就会等待,直到线程B执行完critical_section. Unlock();语句,线程A才会继续执行。
    下面再通过一个实例进行演示说明。

    例程8 MultiThread8
    建立一个基于对话框的工程MultiThread8,在对话框IDD_MULTITHREAD8_DIALOG中加入两个按钮和两个编辑框控件,两个按钮的ID分别为

    IDC_WRITEW和IDC_WRITED,标题分别为“写‘W’”和“写‘D’”;两个编辑框的ID分别为IDC_W和IDC_D,属性都选中Read-only;

    在MultiThread8Dlg.h文件中声明两个线程函数: UINT WriteW(LPVOID pParam);
    UINT WriteD(LPVOID pParam);
    使用ClassWizard分别给IDC_W和IDC_D添加CEdit类变量m_ctrlW和m_ctrlD;
    在MultiThread8Dlg.cpp文件中添加如下内容:
    为了文件中能够正确使用同步类,在文件开头添加:#include "afxmt.h"
    定义临界区和一个字符数组,为了能够在不同线程间使用,定义为全局变量:CCriticalSection critical_section;
    char g_Array[10];
    添加线程函数:UINT WriteW(LPVOID pParam)
    {
    CEdit *pEdit=(CEdit*)pParam;
    pEdit->SetWindowText("");
    critical_section.Lock();
    //锁定临界区,其它线程遇到critical_section.Lock();语句时要等待
    //直至执行critical_section.Unlock();语句
    for(int i=0;i<10;i++)
    {
    g_Array[i]=''W'';
         pEdit->SetWindowText(g_Array);
    Sleep(1000);
    }
    critical_section.Unlock();
    return 0;
    }
    UINT WriteD(LPVOID pParam)
    {
    CEdit *pEdit=(CEdit*)pParam;
    pEdit->SetWindowText("");
    critical_section.Lock();
    //锁定临界区,其它线程遇到critical_section.Lock();语句时要等待
    //直至执行critical_section.Unlock();语句
    for(int i=0;i<10;i++)
    {
    g_Array[i]=''D'';
         pEdit->SetWindowText(g_Array);
    Sleep(1000);
    }
    critical_section.Unlock();
    return 0;
    }
    分别双击按钮IDC_WRITEW和IDC_WRITED,添加其响应函数: void CMultiThread8Dlg::OnWritew()
    {
    CWinThread *pWriteW=AfxBeginThread(WriteW,
    &m_ctrlW,
    THREAD_PRIORITY_NORMAL,
    0,
    CREATE_SUSPENDED);
    pWriteW->ResumeThread();
    }
    void CMultiThread8Dlg::OnWrited()
    {
    CWinThread *pWriteD=AfxBeginThread(WriteD,
    &m_ctrlD,
    THREAD_PRIORITY_NORMAL,
    0,
    CREATE_SUSPENDED);
    pWriteD->ResumeThread();

    }
    由于代码较简单,不再详述。编译、运行该例程,您可以连续点击两个按钮,观察体会临界类的作用。
    B、使用 CEvent 类
      CEvent
    类提供了对事件的支持。事件是一个允许一个线程在某种情况发生时,唤醒另外一个线程的同步对象。例如在某些网络应用程序中,一个线程

    (记为A)负责监听通讯端口,另外一个线程(记为B)负责更新用户数据。通过使用CEvent
    类,线程A可以通知线程B何时更新用户数据。每一个CEvent 对象可以有两种状态:有信号状态和无信号状态。线程监视位于其中的CEvent
    类对象的状态,并在相应的时候采取相应的操作。
      在MFC中,CEvent 类对象有两种类型:人工事件和自动事件。一个自动CEvent
    对象在被至少一个线程释放后会自动返回到无信号状态;而人工事件对象获得信号后,释放可利用线程,但直到调用成员函数ReSetEvent()才

    将其设置为无信号状态。在创建CEvent
    类的对象时,默认创建的是自动事件。 CEvent 类的各成员函数的原型和参数说明如下:
    1、CEvent(BOOL bInitiallyOwn=FALSE,
              BOOL bManualReset=FALSE,
              LPCTSTR lpszName=NULL,
              LPSECURITY_ATTRIBUTES lpsaAttribute=NULL);

    bInitiallyOwn:指定事件对象初始化状态,TRUE为有信号,FALSE为无信号;
    bManualReset:指定要创建的事件是属于人工事件还是自动事件。TRUE为人工事件,FALSE为自动事件;
    后两个参数一般设为NULL,在此不作过多说明。
    2、BOOL CEvent::SetEvent();
      将 CEvent 类对象的状态设置为有信号状态。如果事件是人工事件,则 CEvent 类对象保持为有信号状态,直到调用成员函数ResetEvent

    ()将
    其重新设为无信号状态时为止。如果CEvent 类对象为自动事件,则在SetEvent()将事件设置为有信号状态后,CEvent
    类对象由系统自动重置为无信号状态。
    如果该函数执行成功,则返回非零值,否则返回零。 3、BOOL CEvent::ResetEvent();
      该函数将事件的状态设置为无信号状态,并保持该状态直至SetEvent()被调用时为止。由于自动事件是由系统自动重置,故自动事件不需

    要调用该函数。如果该函数执行成功,返回非零值,否则返回零。我们一般通过调用WaitForSingleObject函数来监视事件状态。前面我们已经

    介绍了该函数。由于语言描述的原因,CEvent
    类的理解确实有些难度,但您只要通过仔细回味下面例程,多看几遍就可理解。
    例程9 MultiThread9
    建立一个基于对话框的工程MultiThread9,在对话框IDD_MULTITHREAD9_DIALOG中加入一个按钮和两个编辑框控件,按钮的ID为IDC_WRITEW,标

    题为“写‘W’”;两个编辑框的ID分别为IDC_W和IDC_D,属性都选中Read-only;

    在MultiThread9Dlg.h文件中声明两个线程函数: UINT WriteW(LPVOID pParam);
    UINT WriteD(LPVOID pParam);
    使用ClassWizard分别给IDC_W和IDC_D添加CEdit类变量m_ctrlW和m_ctrlD;
    在MultiThread9Dlg.cpp文件中添加如下内容:
    为了文件中能够正确使用同步类,在文件开头添加
    #include "afxmt.h"
    定义事件对象和一个字符数组,为了能够在不同线程间使用,定义为全局变量。 CEvent eventWriteD;
    char g_Array[10];
    添加线程函数: UINT WriteW(LPVOID pParam)
    {
    CEdit *pEdit=(CEdit*)pParam;
    pEdit->SetWindowText("");
    for(int i=0;i<10;i++)
    {
    g_Array[i]=''W'';
         pEdit->SetWindowText(g_Array);
    Sleep(1000);
    }
    eventWriteD.SetEvent();
    return 0;
    }
    UINT WriteD(LPVOID pParam)
    {
    CEdit *pEdit=(CEdit*)pParam;
    pEdit->SetWindowText("");
    WaitForSingleObject(eventWriteD.m_hObject,INFINITE);
    for(int i=0;i<10;i++)
    {
    g_Array[i]=''D'';
         pEdit->SetWindowText(g_Array);
    Sleep(1000);
    }
    return 0;
    }
      仔细分析这两个线程函数, 您就会正确理解CEvent 类。线程WriteD执行到
    WaitForSingleObject(eventWriteD.m_hObject,INFINITE);处等待,直到事件eventWriteD为有信号该线程才往下执行,因为eventWriteD对象

    是自动事件,则当WaitForSingleObject()返回时,系统自动把eventWriteD对象重置为无信号状态。

    双击按钮IDC_WRITEW,添加其响应函数: void CMultiThread9Dlg::OnWritew()
    {
    CWinThread *pWriteW=AfxBeginThread(WriteW,
    &m_ctrlW,
    THREAD_PRIORITY_NORMAL,
    0,
    CREATE_SUSPENDED);
    pWriteW->ResumeThread();
    CWinThread *pWriteD=AfxBeginThread(WriteD,
    &m_ctrlD,
    THREAD_PRIORITY_NORMAL,
    0,
    CREATE_SUSPENDED);
    pWriteD->ResumeThread();

    }
    编译并运行程序,单击“写‘W’”按钮,体会事件对象的作用。
    C、使用CMutex 类
      互斥对象与临界区对象很像.互斥对象与临界区对象的不同在于:互斥对象可以在进程间使用,而临界区对象只能在同一进程的各线程间使用

    。当然,互斥对象也可以用于同一进程的各个线程间,但是在这种情况下,使用临界区会更节省系统资源,更有效率。
    D、使用CSemaphore 类
      当需要一个计数器来限制可以使用某个线程的数目时,可以使用“信号量”对象。CSemaphore
    类的对象保存了对当前访问某一指定资源的线程的计数值,该计数值是当前还可以使用该资源的线程的数目。如果这个计数达到了零,则所有

    对这个CSemaphore
    类对象所控制的资源的访问尝试都被放入到一个队列中等待,直到超时或计数值不为零时为止。一个线程被释放已访问了被保护的资源时,计

    数值减1;一个线程完成了对被控共享资源的访问时,计数值增1。这个被CSemaphore
    类对象所控制的资源可以同时接受访问的最大线程数在该对象的构建函数中指定。
    CSemaphore 类的构造函数原型及参数说明如下:
    CSemaphore (LONG lInitialCount=1,
                LONG lMaxCount=1,
                LPCTSTR pstrName=NULL,
                LPSECURITY_ATTRIBUTES lpsaAttributes=NULL);
    lInitialCount:信号量对象的初始计数值,即可访问线程数目的初始值;
    lMaxCount:信号量对象计数值的最大值,该参数决定了同一时刻可访问由信号量保护的资源的线程最大数目;
    后两个参数在同一进程中使用一般为NULL,不作过多讨论;
      在用CSemaphore
    类的构造函数创建信号量对象时要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增

    加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减

    小到0时,则说明当前占用资源的线程数已经达到了所允许的最大数目,不能再允许其它线程的进入,此时的信号量信号将无法发出。线程在处

    理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源数加1。
    下面给出一个简单实例来说明 CSemaphore 类的用法。
    例程10 MultiThread10
    建立一个基于对话框的工程MultiThread10,在对话框IDD_MULTITHREAD10_DIALOG中加入一个按钮和三个编辑框控件,按钮的ID为IDC_START,

    标题为“同时写‘A’、‘B’、‘C’”;三个编辑框的ID分别为IDC_A、IDC_B和IDC_C,属性都选中Read-only;

    在MultiThread10Dlg.h文件中声明两个线程函数: UINT WriteA(LPVOID pParam);
    UINT WriteB(LPVOID pParam);
    UINT WriteC(LPVOID pParam);
    使用ClassWizard分别给IDC_A、IDC_B和IDC_C添加CEdit类变量m_ctrlA、m_ctrlB和m_ctrlC;
    在MultiThread10Dlg.cpp文件中添加如下内容:
    为了文件中能够正确使用同步类,在文件开头添加:
    #include "afxmt.h"
    定义信号量对象和一个字符数组,为了能够在不同线程间使用,定义为全局变量:CSemaphore semaphoreWrite(2,2);
    //资源最多访问线程2个,当前可访问线程数2个
    char g_Array[10];
    添加三个线程函数:
    UINT WriteA(LPVOID pParam)
    {
    CEdit *pEdit=(CEdit*)pParam;
    pEdit->SetWindowText("");
    WaitForSingleObject(semaphoreWrite.m_hObject,INFINITE);
    CString str;
    for(int i=0;i<10;i++)
    {
            pEdit->GetWindowText(str);
    g_Array[i]=''A'';
    str=str+g_Array[i];
         pEdit->SetWindowText(str);
    Sleep(1000);
    }
    ReleaseSemaphore(semaphoreWrite.m_hObject,1,NULL);
    return 0;
    }
    UINT WriteB(LPVOID pParam)
    {
    CEdit *pEdit=(CEdit*)pParam;
    pEdit->SetWindowText("");
    WaitForSingleObject(semaphoreWrite.m_hObject,INFINITE);
    CString str;
    for(int i=0;i<10;i++)
    {
            pEdit->GetWindowText(str);
    g_Array[i]=''B'';
    str=str+g_Array[i];
         pEdit->SetWindowText(str);
    Sleep(1000);
    }
    ReleaseSemaphore(semaphoreWrite.m_hObject,1,NULL);
    return 0;
    }
    UINT WriteC(LPVOID pParam)
    {
    CEdit *pEdit=(CEdit*)pParam;
    pEdit->SetWindowText("");
    WaitForSingleObject(semaphoreWrite.m_hObject,INFINITE);
    for(int i=0;i<10;i++)
    {
    g_Array[i]=''C'';
         pEdit->SetWindowText(g_Array);
    Sleep(1000);
    }
    ReleaseSemaphore(semaphoreWrite.m_hObject,1,NULL);
    return 0;
    }
    这三个线程函数不再多说。在信号量对象有信号的状态下,线程执行到WaitForSingleObject语句处继续执行,同时可用线程数减1;若线程执

    行到WaitForSingleObject语句时信号量对象无信号,线程就在这里等待,直到信号量对象有信号线程才往下执行。

    双击按钮IDC_START,添加其响应函数: void CMultiThread10Dlg::OnStart()
    {
    CWinThread *pWriteA=AfxBeginThread(WriteA,
    &m_ctrlA,
    THREAD_PRIORITY_NORMAL,
    0,
    CREATE_SUSPENDED);
    pWriteA->ResumeThread();
    CWinThread *pWriteB=AfxBeginThread(WriteB,
    &m_ctrlB,
    THREAD_PRIORITY_NORMAL,
    0,
    CREATE_SUSPENDED);
    pWriteB->ResumeThread();
    CWinThread *pWriteC=AfxBeginThread(WriteC,
    &m_ctrlC,
    THREAD_PRIORITY_NORMAL,
    0,
    CREATE_SUSPENDED);
    pWriteC->ResumeThread();

    }
    好吧,多线程编程就介绍到这里,希望本文能对您有所帮助。
    展开全文
  • 线程编程要点

    千次阅读 2005-11-22 16:20:00
    线程编程要点线程是进程的一条执行路径,它包含独立的堆栈和CPU寄存器状态,每个线程共享所有的进程资源,包括打开的文件、信号标识及动态分配的内存等。一个进程内的所有线程使用同一个地址空间,而这些线程的...

    多线程编程要点

    线程是进程的一条执行路径,它包含独立的堆栈和CPU寄存器状态,每个线程共享所有的进程资源,包括打开的文件、信号标识及动态分配的内存等。一个进程内的所有线程使用同一个地址空间,而这些线程的执行由系统调度程序控制,调度程序决定哪个线程可执行以及什么时候执行线程。线程有优先级别,优先权较低的线程必须等到优先权较高的线程执行完后再执行。在多处理器的机器上,调度程序可将多个线程放到不同的处理器上去运行,这样可使处理器任务平衡,并提高系统的运行效率。

    Windows是一种多任务的操作系统,在Windows的一个进程内包含一个或多个线程。32位Windows环境下的Win32 API提供了多线程应用程序开发所需要的接口函数,而利用VC中提供的标准C库也可以开发多线程应用程序,相应的MFC类库封装了多线程编程的类,用户在开发时可根据应用程序的需要和特点选择相应的工具。为了使大家能全面地了解Windows多线程编程技术,本文将重点介绍Win32 API和MFC两种方式下如何编制多线程程序。

    多线程编程在Win32方式下和MFC类库支持下的原理是一致的,进程的主线程在任何需要的时候都可以创建新的线程。当线程执行完后,自动终止线程; 当进程结束后,所有的线程都终止。所有活动的线程共享进程的资源,因此,在编程时需要考虑在多个线程访问同一资源时产生冲突的问题。当一个线程正在访问某进程对象,而另一个线程要改变该对象,就可能会产生错误的结果,编程时要解决这个冲突。

    Win32 API下的多线程编程

    Win32 API是Windows操作系统内核与应用程序之间的界面,它将内核提供的功能进行函数包装,应用程序通过调用相关函数而获得相应的系统功能。为了向应用程序提供多线程功能,Win32 API函数集中提供了一些处理多线程程序的函数集。直接用Win32 API进行程序设计具有很多优点: 基于Win32的应用程序执行代码小,运行效率高,但是它要求程序员编写的代码较多,且需要管理所有系统提供给程序的资源。用Win32 API直接编写程序要求程序员对Windows系统内核有一定的了解,会占用程序员很多时间对系统资源进行管理,因而程序员的工作效率降低。

    1. 用Win32函数创建和终止线程

    Win32函数库中提供了操作多线程的函数,包括创建线程、终止线程、建立互斥区等。在应用程序的主线程或者其他活动线程中创建新的线程的函数如下:

    HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,DWORD dwStackSize,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter,DWORD dwCreationFlags,LPDWORD lpThreadId);

    如果创建成功则返回线程的句柄,否则返回NULL。创建了新的线程后,该线程就开始启动执行了。但如果在dwCreationFlags中使用了CREATE_SUSPENDED特性,那么线程并不马上执行,而是先挂起,等到调用ResumeThread后才开始启动线程,在这个过程中可以调用下面这个函数来设置线程的优先权:

    BOOL SetThreadPriority(HANDLE hThread,int nPriority);

    当调用线程的函数返回后,线程自动终止。如果需要在线程的执行过程中终止则可调用函数:

    VOID ExitThread(DWORD dwExitCode);

    如果在线程的外面终止线程,则可调用下面的函数:

    BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode);

    但应注意: 该函数可能会引起系统不稳定,而且线程所占用的资源也不释放。因此,一般情况下,建议不要使用该函数。

    如果要终止的线程是进程内的最后一个线程,则线程被终止后相应的进程也应终止。

    2. 线程的同步

    在线程体内,如果该线程完全独立,与其他线程没有数据存取等资源操作上的冲突,则可按照通常单线程的方法进行编程。但是,在多线程处理时情况常常不是这样,线程之间经常要同时访问一些资源。由于对共享资源进行访问引起冲突是不可避免的,为了解决这种线程同步问题,Win32 API提供了多种同步控制对象来帮助程序员解决共享资源访问冲突。在介绍这些同步对象之前先介绍一下等待函数,因为所有控制对象的访问控制都要用到这个函数。

    Win32 API提供了一组能使线程阻塞其自身执行的等待函数。这些函数在其参数中的一个或多个同步对象产生了信号,或者超过规定的等待时间才会返回。在等待函数未返回时,线程处于等待状态,此时线程只消耗很少的CPU时间。使用等待函数既可以保证线程的同步,又可以提高程序的运行效率。最常用的等待函数是:

    DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);

    而函数WaitForMultipleObject可以用来同时监测多个同步对象,该函数的声明为:

    DWORD WaitForMultipleObject(DWORD nCount,CONST HANDLE *lpHandles,BOOL bWaitAll,DWORD dwMilliseconds);

    (1)互斥体对象

    Mutex对象的状态在它不被任何线程拥有时才有信号,而当它被拥有时则无信号。Mutex对象很适合用来协调多个线程对共享资源的互斥访问。可按下列步骤使用该对象:

    首先,建立互斥体对象,得到句柄:

    HANDLE CreateMutex();

    然后,在线程可能产生冲突的区域前(即访问共享资源之前)调用WaitForSingleObject,将句柄传给函数,请求占用互斥对象:

    dwWaitResult = WaitForSingleObject(hMutex,5000L);

    共享资源访问结束,释放对互斥体对象的占用:

    ReleaseMutex(hMutex);

    互斥体对象在同一时刻只能被一个线程占用,当互斥体对象被一个线程占用时,若有另一线程想占用它,则必须等到前一线程释放后才能成功。

    (2)信号对象

    信号对象允许同时对多个线程共享资源进行访问,在创建对象时指定最大可同时访问的线程数。当一个线程申请访问成功后,信号对象中的计数器减一,调用ReleaseSemaphore函数后,信号对象中的计数器加一。其中,计数器值大于或等于0,但小于或等于创建时指定的最大值。如果一个应用在创建一个信号对象时,将其计数器的初始值设为0,就阻塞了其他线程,保护了资源。等初始化完成后,调用ReleaseSemaphore函数将其计数器增加至最大值,则可进行正常的存取访问。可按下列步骤使用该对象:

    首先,创建信号对象:

    HANDLE CreateSemaphore();

    或者打开一个信号对象:

    HANDLE OpenSemaphore();

    然后,在线程访问共享资源之前调用WaitForSingleObject。

    共享资源访问完成后,应释放对信号对象的占用:

    ReleaseSemaphore();

    (3)事件对象

    事件对象(Event)是最简单的同步对象,它包括有信号和无信号两种状态。在线程访问某一资源之前,需要等待某一事件的发生,这时用事件对象最合适。例如:只有在通信端口缓冲区收到数据后,监视线程才被激活。

    事件对象是用CreateEvent函数建立的。该函数可以指定事件对象的类和事件的初始状态。如果是手工重置事件,那么它总是保持有信号状态,直到用ResetEvent函数重置成无信号的事件。如果是自动重置事件,那么它的状态在单个等待线程释放后会自动变为无信号的。用SetEvent可以把事件对象设置成有信号状态。在建立事件时,可以为对象命名,这样其他进程中的线程可以用OpenEvent函数打开指定名字的事件对象句柄。

    (4)排斥区对象

    在排斥区中异步执行时,它只能在同一进程的线程之间共享资源处理。虽然此时上面介绍的几种方法均可使用,但是,使用排斥区的方法则使同步管理的效率更高。

    使用时先定义一个CRITICAL_SECTION结构的排斥区对象,在进程使用之前调用如下函数对对象进行初始化:

    VOID InitializeCriticalSection(LPCRITICAL_SECTION);

    当一个线程使用排斥区时,调用函数:EnterCriticalSection或者TryEnterCriticalSection;

    当要求占用、退出排斥区时,调用函数LeaveCriticalSection,释放对排斥区对象的占用,供其他线程使用。

    基于MFC的多线程编程

    MFC是微软的VC开发集成环境中提供给程序员的基础函数库,它用类库的方式将Win32 API进行封装,以类的方式提供给开发者。由于其快速、简捷、功能强大等特点深受广大开发者喜爱。因此,建议使用MFC类库进行应用程序的开发。

    在VC++附带的MFC类库中,提供了对多线程编程的支持,基本原理与基于Win32 API的设计一致,但由于MFC对同步对象做了封装,因此实现起来更加方便,避免了对象句柄管理上的烦琐工作。

    在MFC中,线程分为两种:工作线程和用户接口线程。工作线程与前面所述的线程一致,用户接口线程是一种能够接收用户的输入、处理事件和消息的线程。

    1. 工作线程

    工作线程编程较为简单,设计思路与前面所讲的基本一致: 一个基本函数代表了一个线程,创建并启动线程后,线程进入运行状态; 如果线程用到共享资源,则需要进行资源同步处理。这种方式创建线程并启动线程时可调用函数:

    CWinThread*AfxBeginThread( AFX_THREADPROC pfnThreadProc, LPVOID pParam,int nPriority= THREAD_PRIORITY_NORMAL,UINT nStackSize =0,DWORD dwCreateFlags=0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL);参数pfnThreadProc是线程执行体函数,函数原形为: UINT ThreadFunction( LPVOID pParam)。

    参数pParam是传递给执行函数的参数;

    参数nPriority是线程执行权限,可选值:

    THREAD_PRIORITY_NORMAL、THREAD_PRIORITY_LOWEST、THREAD_PRIORITY_HIGHEST、THREAD_PRIORITY_IDLE。

    参数dwCreateFlags是线程创建时的标志,可取值CREATE_SUSPENDED,表示线程创建后处于挂起状态,调用ResumeThread函数后线程继续运行,或者取值“0”表示线程创建后处于运行状态。

    返回值是CWinThread类对象指针,它的成员变量m_hThread为线程句柄,在Win32 API方式下对线程操作的函数参数都要求提供线程的句柄,所以当线程创建后可以使用所有Win32 API函数对pWinThread->m_Thread线程进行相关操作。

    注意:如果在一个类对象中创建和启动线程时,应将线程函数定义成类外的全局函数。

    2. 用户接口线程

    基于MFC的应用程序有一个应用对象,它是CWinApp派生类的对象,该对象代表了应用进程的主线程。当线程执行完并退出线程时,由于进程中没有其他线程存在,进程自动结束。类CWinApp从CWinThread派生出来,CWinThread是用户接口线程的基本类。我们在编写用户接口线程时,需要从CWinThread派生我们自己的线程类,ClassWizard可以帮助我们完成这个工作。

    先用ClassWizard派生一个新的类,设置基类为CwinThread。注意:类的DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE宏是必需的,因为创建线程时需要动态创建类的对象。根据需要可将初始化和结束代码分别放在类的InitInstance和ExitInstance函数中。如果需要创建窗口,则可在InitInstance函数中完成。然后创建线程并启动线程。可以用两种方法来创建用户接口线程,MFC提供了两个版本的AfxBeginThread函数,其中一个用于创建用户接口线程。第二种方法分为两步进行:首先,调用线程类的构造函数创建一个线程对象;其次,调用CWinThread::CreateThread函数来创建该线程。线程建立并启动后,在线程函数执行过程中一直有效。如果是线程对象,则在对象删除之前,先结束线程。CWinThread已经为我们完成了线程结束的工作。

    3. 线程同步

    前面我们介绍了Win32 API提供的几种有关线程同步的对象,在MFC类库中对这几个对象进行了类封装,它们有一个共同的基类CSyncObject,它们的对应关系为: Semaphore对应CSemaphore、Mutex对应CMutex、Event对应CEvent、CriticalSection对应CCriticalSection。另外,MFC对两个等待函数也进行了封装,即CSingleLock和CMultiLock。因四个对象用法相似,在这里就以CMutex为例进行说明:

    创建一个CMutex对象:

    CMutex mutex(FALSE,NULL,NULL);

    或CMutex mutex;

    当各线程要访问共享资源时使用下面代码:

    CSingleLock sl(&mutex);

    sl.Lock();

    if(sl.IsLocked())

    //对共享资源进行操作...

    sl.Unlock();

    结束语

    如果用户的应用程序需要多个任务同时进行相应的处理,则使用多线程是较理想的选择。这里,提醒大家注意的是在多线程编程时要特别小心处理资源共享问题以及多线程调试问题

    展开全文
  • Windows平台下多线程编程基本步骤

    千次阅读 2012-12-03 20:15:57
    由于Windows是一个以虚拟内存为基础的操作系统,在系统环境下,windows内存管理器在内存中来回移动对象,对象被移动也就是对象的地址变化了。Windows为各应用程序腾出一些内存储地址,用来专门登记各应用对象在内存...
  • 很早以前就先写写linux下多线程编程和windows下的多线程编程了,但是每当写时又不知道从哪个地方写起,怎样把自己知道的东西都写出来,下面我就谈谈linux多线程及线程同步,并将它和windows的多线程进行比较,看看...
  • vc6.0 线程编程

    千次阅读 2011-07-16 14:02:25
    留个纪念,不错的总结。十个例子清晰列举啦多线程编程的奥妙。 VC中多线程使用比较广泛...一、问题的提出编写一个耗时的单线程程序: 新建一个基于对话框的应用程序SingleThread,在主对话框IDD_SINGLETHREAD_DIALOG
  • python多线程编程

    千次阅读 2016-05-22 13:27:18
    在最开始的时候,计算机都是只有一个cpu来进行指令控制和运算,程序执行的时候都是一个进程一个进程的运行,也就是顺序执行的方式,所有的进程都是排成一个队列,然后cpu取出其中的一个进程,然后运行。 在硬件发展...
  • 简单而强大的多线程串口编程工具CserialPort类(附VC基于MFC单文档协议通讯源程序及详细编程步骤)    老有人觉得MSComm通讯控件很土,更有人大声疾呼:忘了它吧。确实当我们对串口编程有...
  • Python多线程编程

    千次阅读 2019-02-28 11:46:07
    在多线程(multithreaded, MT)编程出现之前,计算机程序的执行是由单个步骤序列组成的,该序列在主机的 CPU 中按照同步顺序执行。无论是任务本身需要按照步骤顺序执行,还是整个程序实际上包含多子任务,都需要...
  • 线程编程示例: 具体内容创建3个线程分别以不同的速率计数每满 100画一个椭圆 效果: 效果: 步骤visual stdio2010 中创建一个 win32控制台程序 ThreadDomo--- 取消窗口的最大化按钮---在 WM_CREATE消息中创建edit框...
  • Java多线程编程 深入详解

    千次阅读 多人点赞 2018-01-17 21:59:54
    《Java多线程编程 深入详解》读书笔记 第章 多进程多线程概述 线程概述 进程(PROCESS):CPU的执行路径 多进程:系统中同时运行多程序 线程(THREAD):运行在进程当中的运行单元 多线程...
  • VC++多线程编程

    千次阅读 2018-07-21 14:14:29
    十个例子清晰列举啦多线程编程的奥妙。 VC中多线程使用比较广泛而且实用,在网上看到的教程.感觉写的挺好. 一、问题的提出 编写一个耗时的单线程程序:  新建一个基于对话框的应用程序SingleThread,在主对话框...
  • 线程编程技术

    千次阅读 2008-10-27 16:35:00
    转自:http://blog.csdn.net/fengzhihua_395/archive/2008/01/10/2033814.aspx多线程编程技术 线程是比进程更小的单位,可以认为进程是由一个或多个线程组成的。据说以前的 400 版本并不支持真正的多线程技术,在 4.2...
  • C#多线程编程

    千次阅读 2019-06-20 16:02:12
    线程线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。 多线程:多线程是指程序中包含多个执行流,即在一个程序中可以同时...
  • Windows平台下的多线程编程

    千次阅读 2016-04-12 20:56:42
    一个进程内的所有线程使用同一个地址空间,而这些线程的执行由系统调度程序控制,调度程序决定哪个线程可执行以及什么时候执行线程线程有优先级别,优先权较低的线程必须等到优先权较高的线程执行完后再执行。在多...
  • VC多线程编程

    千次阅读 2007-06-06 12:32:00
    VC多线程编程(转)VC中多线程使用比较广泛而且实用,在网上看到的教程.感觉写的挺好. 一、问题的提出编写一个耗时的单线程程序: 新建一个基于对话框的应用程序SingleThread,在主对话框IDD_SINGLETHREAD_DIALOG添加...
  • 《C#多线程编程实战》读书笔记

    千次阅读 多人点赞 2018-01-13 00:26:49
    本文是篇读书笔记,由《C#多线程编程实战》书中的内容整理而来,主要梳理了.NET中多线程编程相关的知识脉络,从Thread、ThreadPool、Task、async/await、并发集合、Parallel、PLINQ到Rx及异步I/O等内容,均有所...
  • Windows多线程编程

    千次阅读 2008-12-03 10:31:00
    线程编程线程编程之一——问题提出一、问题的提出编写一个耗时的单线程程序: 新建一个基于对话框的应用程序SingleThread,在主对话框IDD_SINGLETHREAD_DIALOG添加一个按钮,ID为IDC_SLEEP_SIX_SECOND,标题为...
  • Android多线程编程和线程池

    千次阅读 2018-12-16 17:08:56
    线程在Android中是一个很重要的概念,从用途上来说,线程分为主线程和子 线程,主线程主要处理和界面UI相关的事,而子线程则往往用于执行耗时操作。由于Android的特性,如果在主线程中执行耗时操作那么就会导致程序...
  • iOS多线程编程指南 线程管理

    千次阅读 2013-04-19 16:51:33
    线程管理 Mac OS X和iOS里面的每个进程都是有一个或多个线程构成,每个线程都代表一个代码的执行路径。每个应用程序启动时候都是一个线程,它执行程序的main函数。应用程序可以生成额外的
  • 线程编程(三)NSOperationQueue

    千次阅读 2016-06-21 10:46:59
    NSOperationQueue NSOperation 多线程编程 任务和队列 依赖
  • Java多线程编程-(1)-线程安全和锁Synchronized概念 Java多线程编程-(2)-可重入锁以及Synchronized的其他基本特性 Java多线程编程-(3)-线程本地ThreadLocal的介绍与使用 Java多线程编程-(4)-线程间通信...
  • C++多线程编程之五

    千次阅读 2013-12-10 17:12:39
    五、MFC对多线程编程的支持  MFC中有两类线程,分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。  工作者线程没有消息机制...
  • 多进程和多线程编程操作

    千次阅读 2018-08-18 17:17:22
    线程是计算机程序运行的实际执行者,是cpu的执行单元,在计算机中,进程主要是为了线程的执行进行分配资源操作,程序真正的执行者是线程,每一个进程至少有一个线程用于执行程序。一个进程可以有多个线程,而一个...
  • win32多线程编程

    千次阅读 2012-08-14 07:00:12
     从单进程单线程到多进程多线程是操作系统发展的种必然趋势,当年的DOS系统属于单任务操作系统,最优秀的程序员也只能通过驻留内存的方式实现所谓的"多任务",而如今的Win32操作系统却可以一边听音乐,一边编程,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 86,675
精华内容 34,670
关键字:

启动一个线程的编程步骤