• 多线程编程(转) - [C++学习]2008年12月23日

    版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
    http://lxdawn.blogbus.com/logs/32850932.html

    二,多线程编程:

    1,一些概念:
    1)一个进程就是一个运行的程序,进程具有独立的内存、文件句柄和其它系统资源。
    2)一个独立的进程可以包含多条执行路径,称为线程。线程有OS管理,每个线程有自己的堆栈。
    3)大多数情况下,进程的所有代码和数据空间被进程内所有的线程所共享。
    4)Windows提供两中线程:辅助线程 和 用户界面线程。mfc对两种线程都支持。
    5)一个用户界面线程有窗口,因而有自己的消息循环;辅助线程没有窗口,因而不需要消息处理。

    2,启动辅助线程:
    准备工作:写一个全局函数。格式:UINT MyControllingFunction( LPVOID pParam );
    启动辅助线程:使用AfxBeginThread函数为全局函数MyControllingFunction创建线程。
    说明一:
    MFC 通过参数重载提供两个版本的 AfxBeginThread:一个用于用户界面线程,另一个用于辅助线程。
    //用户界面线程通常用于处理用户输入和响应用户事件,这些行为独立于执行该应用程序其他部分的线程。已经创建并启动主应用程序线程(在 CWinApp 导出的类中提供)。
    //辅助线程通常用于处理后台任务,用户不必等待就可以继续使用应用程序。重新计算和后台打印等任务是很好的辅助线程示例。
    说明二:
    创建用户界面线程比创建辅助复杂(需要重写一些相关的函数),这里只笔记创建辅助线程一些笔记。
    1)AfxBeginThread
    CWinThread* AfxBeginThread( AFX_THREADPROC pfnThreadProc, LPVOID pParam, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );
    //Return Value:Pointer to the newly created thread object.
    //pfnThreadProc:Points to the controlling function for the worker thread. Cannot be NULL. This function must be declared as follows: UINT MyControllingFunction( LPVOID pParam );
    //pParam:Parameter to be passed to the controlling function as shown in the parameter to the function declaration in pfnThreadProc.
    //nPriority:The desired priority(优先权) of the thread. If 0, the same priority as the creating thread will be used. For a full list and description of the available priorities, seeSetThreadPriority in the Win32 Programmer’s Reference.
    //nStackSize:Specifies the size in bytes of the stack for the new thread. If 0, the stack size defaults to the same size stack as the creating thread.
    //dwCreateFlags:Specifies an additional flag that controls the creation of the thread. This flag can contain one of two values:
    [CREATE_SUSPENDED] Start the thread with a suspend count of one. The thread will not execute until ResumeThread is called.
    [ 0 ] Start the thread immediately after creation.
    //lpSecurityAttrs:Points to a SECURITY_ATTRIBUTES structure that specifies the security attributes for the thread. If NULL, the same security attributes as the creating thread will be used.
    //Remarks:
    Call this function to create a new thread. The first form of AfxBeginThread creates a worker thread. The second form creates a user-interface thread.
    To end the thread, call AfxEndThread from within the thread, or return from the controlling function of the worker thread.
    2)挂起线程:
    CWinThread::SuspendThread
    DWORD SuspendThread( );
    //Return Value:The thread’s previous suspend count if successful; 0xFFFFFFFF otherwise.
    //CWinThread::SuspendThread:Increments the current thread’s suspend(悬挂,延缓) count. If any thread has a suspend count above zero, that thread does not execute.
    3)恢复线程运行:
    CWinThread::ResumeThread
    DWORD ResumeThread( );
    //Return Value:The thread’s previous suspend count if successful; 0xFFFFFFFF otherwise. If the return value is zero, the current thread was not suspended. If the return value is one, the thread was suspended, but is now restarted. Any return value greater than one means the thread remains suspended.
    //Remarks:Called to resume(恢复) execution of a thread that was suspended(暂停,延缓) by the SuspendThread member function, or a thread created with the CREATE_SUSPENDED flag. The suspend count of the current thread is reduced by one. If the suspend count is reduced to zero, the thread resumes execution; otherwise the thread remains suspended.
    4)创建辅助线程事例:
    UINT MyThreadProc( LPVOID pParam )
    {
    CMyObject* pObject = (CMyObject*)pParam;
    if (pObject == NULL ||
    !pObject->IsKindOf(RUNTIME_CLASS(CMyObject)))
    return 1; // if pObject is not valid
    // do something with 'pObject'
    return 0; // thread completed successfully
    }
    // inside a different function in the program
    ...
    pNewObject = new CMyObject;
    AfxBeginThread(MyThreadProc, pNewObject);

    3,主线程 和 辅助线程 的通话:(这里的主线程指应用程序,是个用户界面线程)
    1)最简单的方法:使用全局变量。
    (注 意:如书上事例B中在一个辅助线程中使用全局的计数器,不希望其它现在在计数器递增的时候由于其它线程访问而引起混乱,则将起声明成volatile变量 保证计数器不被保存到寄存器中,也可以使用InterlockedIncrement来阻塞其它线程同时使计数器递增。)
    补充一:
    InterlockedIncrement
    LONG InterlockedIncrement(
    LPLONG lpAddend // pointer to the variable to increment
    );
    //The InterlockedIncrement function both increments (increases by one) the value of the specified 32-bit variable and checks the resulting value. The function prevents more than one thread from using the same variable simultaneously.
    补充二:
    volatile
    使用 volatile 修饰符能够确保一个线程检索由另一线程写入的最新值。
    //当字段声明中含有 volatile 修饰符时,该声明引入的字段为易失字段。

    由 于采用了优化技术(它会重新安排指令的执行顺序),在多线程的程序运行环境下,如果不采取同步控制手段,则对于非易失字段的访问可能会导致意外的和不可预 见的结果。这些优化可以由编译器、运行时系统或硬件执行。但是,对于易失字段,优化时的这种重新排序必须遵循以下规则:

    读取一个易失字段称为易失读取。易失读取具有“获取语义”;也就是说,按照指令序列,所有排在易失读取之后的对内存的引用,在执行时也一定排在它的后面。
    写入一个易失字段称为易失写入。易失写入具有“释放语义”;也就是说,按照指令序列,所有排在易失写入之前的对内存的引用,在执行时也一定排在它的前面。
    这些限制能确保所有线程都会观察到由其他任何线程所执行的易失写入(按照原来安排的顺序)。一个遵循本规范的实现并非必须做到:使易失写入的执行顺序,在所有正在执行的线程看来都是一样的。易失字段的类型必须是下列类型中的一种:
    引用类型。

    类型 byte、sbyte、short、ushort、int、uint、char、float 或 bool。
    枚举基类型为 byte、sbyte、short、ushort、int 或 uint 的枚举类型。
    (China msdn-C# 语言规范 )

    2)不能使用Windows消息(即不能主线程向辅助线程发送消息通信),辅助线程没有窗口没有消息循环。


    4,辅助线程 和 主线程(用户界面线程) 通信
    1)辅助线程向主线程(用户界面线程)发送Windows消息,由主线程响应该消息,从而实现通信。(主线程有一个窗口,可见或不可见,如果辅助线程可以得到主线程的窗口句柄,便可以向主线程发送Windows消息了。主线程总是有一个消息循环的。)
    2)辅助线程可以通过AfxBeginThread函数参数传入主线程句柄从而得到主线程的句柄。
    3)辅助线程使用寄出(post)消息。任何用户定义的消息都可以。(使用送出(SEND)消息会引起主线程MFC消息处理代码的重入,这在模式对话框中会出现问题。)


    5,EX11B事例说明:
    1)CComputeDlg::OnStart 函数中,利用AfxBeginThread(ComputeThreadProc, GetSafeHwnd(),THREAD_PRIORITY_NORMAL);函数为用户自定义的全局函数ComputeThreadProc创建辅助 线程的同时,利用GetSafeHwnd()获得对话框句柄并做为参数传入
    ComputeThreadProc函数形参pParam中。
    //GetSafeHwnd() Returns the window handle for a window. Returns NULL if the CWnd is not attached to a window or if it is used with a NULL CWnd pointer.
    2)UINT ComputeThreadProc(LPVOID pParam)函数中利用传进来的参数pParam,调用::PostMessage((HWND) pParam, WM_THREADFINISHED, 0, 0)函数向对话框窗口发送消息用户自定义WM_THREADFINISHED消息。
    3)在对话框类中为WM_THREADFINISHED添加控制函数。
    三步:
    消 息控制函数声明:CComputeDlg类头文件 LRESULT OnThreadFinished(WPARAM wParam, LPARAM lParam);消息映射:CComputeDlg类代码文件 ON_MESSAGE(WM_THREADFINISHED, OnThreadFinished)
    消息控制函数:CComputeDlg类代码文件
    LRESULT CComputeDlg::OnThreadFinished(WPARAM wParam, LPARAM lParam)
    {
    CDialog::OnOK();
    return 0;
    }

    6,排斥区(CCriticalSection)
    MFC提供了CCriticalSection类来帮助我们实现在线程之间共享全局数据(保证对其临界访问).
    使用方法下面代码演示:
    CCriticalSection g_cs;//定义g_cs为临界访问对象
    int g_nCount;
    voit func()
    {
    g_cs.Lock();
    g_nCount++;
    g_cs.Unlock();
    }
    说明:
    1)CCriticalSection从CSyncObject类派生而来:
    An object of class, CCriticalSection represents a "critical section" - a synchronization object that allows one thread at a time to access a resource or section of code. Critical sections are useful when only one thread at a time can be allowed to modify data or some other controlled resource.
    2)构造函数CCriticalSection( )说明:
    Constructs a CCriticalSection object. To access or release a CCriticalSection object, create a CSingleLock object and call its Lock and Unlock member functions. If the CCriticalSection object is being used stand-alone, call its Unlock member function to release it.
    3)CCriticalSection::Unlock: Releases the CCriticalSection object.
    CCriticalSection::Lock: Use to gain access to the CCriticalSection object.
    4)进一步使用说明:
    当线程A正在执行func()函数使g_nCount++增1的时候,线程B调用func()函数执行到g_cs.Lock()的时候线程B被阻塞直到线程A执行了g_cs.Unlock()才继续往下执行g_nCount++。
    5)CCriticalSection只是用于当个进程内的控制访问,如果要在不同的进程之间控制数据的访问需要使用 互斥体(CMutex) 和 信号(semaphore)。


    7,互斥体(CMutex)
    1)CMutex:
    An object of class CMutex represents a “mutex” — a synchronization object that allows one thread mutually exclusive access to a resource. Mutexes are useful when only one thread at a time can be allowed to modify data or some other controlled resource.
    2)CMutex类从CSyncObject类派生而来,其一般用法参见下E文:
    To use a CMutex object, construct the CMutex object when it is needed. Specify the name of the mutex you wish to wait on, and that your application should initially own it. You can then access the mutex when the constructor returns. Call CSyncObject::Unlock when you are done accessing the controlled resource.


    8,信号(CSemaphore)
    1)CSemaphore(也是从CSyncObject类派生而来):
    An object of class CSemaphore represents a “semaphore” — a synchronization object that allows a limited number of threads in one or more processes to access a resource. A CSemaphore object maintains a count of the number of threads currently accessing a specified resource.
    2)Semaphores are useful in controlling access to a shared resource that can only support a limited number of users.The current count of the CSemaphore object is the number of additional users allowed. When the count reaches zero, all attempts to use the resource controlled by the CSemaphore object will be inserted into a system queue and wait until they either time out or the count rises above 0. The maximum number of users who can access the controlled resource at one time is specified during construction of the CSemaphore object.
    构造函数:
    CSemaphore( LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName = NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL );

    9,关于从CSyncObject类派生类一些说明:
    1)CCriticalSection,CMutex,CSemaphore,CEvent,derived from CSyncObject。
    2)CSyncObject类成员中包含一下两成员函数:
    Lock Gains access to the synchronization object.
    Unlock Releases access to the synchronization object.

    收藏到:Del.icio.us