各位大虾好:
      采用多线程,公用一个全局变量,访问时,如何对全局变量加锁?
谢谢!

解决方案 »

  1.   

    use CRITICAL_SECTION to lock the shared resource !CRITICAL_SECTION m_lock;InitializeCriticalSection(&m_lock);
    EnterCriticalSection(&m_lock);
    ........
    //access the shared resource
    .....
    LeaveCriticalSection(&m_lock);
    DeleteCriticalSection(&m_lock);may you succeed !
      

  2.   

    临界区  临界区(Critical Section)是一段独占对某些共享资源访问的代码,在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。  临界区在使用时以CRITICAL_SECTION结构对象保护共享资源,并分别用EnterCriticalSection()和LeaveCriticalSection()函数去标识和释放一个临界区。所用到的CRITICAL_SECTION结构对象必须经过InitializeCriticalSection()的初始化后才能使用,而且必须确保所有线程中的任何试图访问此共享资源的代码都处在此临界区的保护之下。否则临界区将不会起到应有的作用,共享资源依然有被破坏的可能。
    图1 使用临界区保持线程同步  下面通过一段代码展示了临界区在保护多线程访问的共享资源中的作用。通过两个线程来分别对全局变量g_cArray[10]进行写入操作,用临界区结构对象g_cs来保持线程的同步,并在开启线程前对其进行初始化。为了使实验效果更加明显,体现出临界区的作用,在线程函数对共享资源g_cArray[10]的写入时,以Sleep()函数延迟1毫秒,使其他线程同其抢占CPU的可能性增大。如果不使用临界区对其进行保护,则共享资源数据将被破坏(参见图1(a)所示计算结果),而使用临界区对线程保持同步后则可以得到正确的结果(参见图1(b)所示计算结果)。代码实现清单附下:// 临界区结构对象
    CRITICAL_SECTION g_cs;
    // 共享资源 
    char g_cArray[10];
    UINT ThreadProc10(LPVOID pParam)
    {
     // 进入临界区
     EnterCriticalSection(&g_cs);
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[i] = 'a';
      Sleep(1);
     }
     // 离开临界区
     LeaveCriticalSection(&g_cs);
     return 0;
    }
    UINT ThreadProc11(LPVOID pParam)
    {
     // 进入临界区
     EnterCriticalSection(&g_cs);
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[10 - i - 1] = 'b';
      Sleep(1);
     }
     // 离开临界区
     LeaveCriticalSection(&g_cs);
     return 0;
    }
    ……
    void CSample08View::OnCriticalSection() 
    {
     // 初始化临界区
     InitializeCriticalSection(&g_cs);
     // 启动线程
     AfxBeginThread(ThreadProc10, NULL);
     AfxBeginThread(ThreadProc11, NULL);
     // 等待计算完毕
     Sleep(300);
     // 报告计算结果
     CString sResult = CString(g_cArray);
     AfxMessageBox(sResult);
    }   在使用临界区时,一般不允许其运行时间过长,只要进入临界区的线程还没有离开,其他所有试图进入此临界区的线程都会被挂起而进入到等待状态,并会在一定程度上影响。程序的运行性能。尤其需要注意的是不要将等待用户输入或是其他一些外界干预的操作包含到临界区。如果进入了临界区却一直没有释放,同样也会引起其他线程的长时间等待。换句话说,在执行了EnterCriticalSection()语句进入临界区后无论发生什么,必须确保与之匹配的LeaveCriticalSection()都能够被执行到。可以通过添加结构化异常处理代码来确保LeaveCriticalSection()语句的执行。虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。  MFC为临界区提供有一个CCriticalSection类,使用该类进行线程同步处理是非常简单的,只需在线程函数中用CCriticalSection类成员函数Lock()和UnLock()标定出被保护代码片段即可。对于上述代码,可通过CCriticalSection类将其改写如下:// MFC临界区类对象
    CCriticalSection g_clsCriticalSection;
    // 共享资源 
    char g_cArray[10];
    UINT ThreadProc20(LPVOID pParam)
    {
     // 进入临界区
     g_clsCriticalSection.Lock();
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[i] = 'a';
      Sleep(1);
     }
     // 离开临界区
     g_clsCriticalSection.Unlock();
     return 0;
    }
    UINT ThreadProc21(LPVOID pParam)
    {
     // 进入临界区
     g_clsCriticalSection.Lock();
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[10 - i - 1] = 'b';
      Sleep(1);
     }
     // 离开临界区
     g_clsCriticalSection.Unlock();
     return 0;
    }
    ……
    void CSample08View::OnCriticalSectionMfc() 
    {
     // 启动线程
     AfxBeginThread(ThreadProc20, NULL);
     AfxBeginThread(ThreadProc21, NULL);
     // 等待计算完毕
     Sleep(300);
     // 报告计算结果
     CString sResult = CString(g_cArray);
     AfxMessageBox(sResult);

      

  3.   

    信号量内核对象  信号量(Semaphore)内核对象对线程的同步方式与前面几种方法不同,它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。在用CreateSemaphore()创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。
    图3 使用信号量对象控制资源  下面结合图例3来演示信号量对象对资源的控制。在图3中,以箭头和白色箭头表示共享资源所允许的最大资源计数和当前可用资源计数。初始如图(a)所示,最大资源计数和当前可用资源计数均为4,此后每增加一个对资源进行访问的线程(用黑色箭头表示)当前资源计数就会相应减1,图(b)即表示的在3个线程对共享资源进行访问时的状态。当进入线程数达到4个时,将如图(c)所示,此时已达到最大资源计数,而当前可用资源计数也已减到0,其他线程无法对共享资源进行访问。在当前占有资源的线程处理完毕而退出后,将会释放出空间,图(d)已有两个线程退出对资源的占有,当前可用计数为2,可以再允许2个线程进入到对资源的处理。可以看出,信号量是通过计数来对线程访问资源进行控制的,而实际上信号量确实也被称作Dijkstra计数器。  使用信号量内核对象进行线程同步主要会用到CreateSemaphore()、OpenSemaphore()、ReleaseSemaphore()、WaitForSingleObject()和WaitForMultipleObjects()等函数。其中,CreateSemaphore()用来创建一个信号量内核对象,其函数原型为:HANDLE CreateSemaphore(
     LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全属性指针
     LONG lInitialCount, // 初始计数
     LONG lMaximumCount, // 最大计数
     LPCTSTR lpName // 对象名指针
    );    参数lMaximumCount是一个有符号32位值,定义了允许的最大资源计数,最大取值不能超过4294967295。lpName参数可以为创建的信号量定义一个名字,由于其创建的是一个内核对象,因此在其他进程中可以通过该名字而得到此信号量。OpenSemaphore()函数即可用来根据信号量名打开在其他进程中创建的信号量,函数原型如下:HANDLE OpenSemaphore(
     DWORD dwDesiredAccess, // 访问标志
     BOOL bInheritHandle, // 继承标志
     LPCTSTR lpName // 信号量名
    );   在线程离开对共享资源的处理时,必须通过ReleaseSemaphore()来增加当前可用资源计数。否则将会出现当前正在处理共享资源的实际线程数并没有达到要限制的数值,而其他线程却因为当前可用资源计数为0而仍无法进入的情况。ReleaseSemaphore()的函数原型为:BOOL ReleaseSemaphore(
     HANDLE hSemaphore, // 信号量句柄
     LONG lReleaseCount, // 计数递增数量
     LPLONG lpPreviousCount // 先前计数
    );   该函数将lReleaseCount中的值添加给信号量的当前资源计数,一般将lReleaseCount设置为1,如果需要也可以设置其他的值。WaitForSingleObject()和WaitForMultipleObjects()主要用在试图进入共享资源的线程函数入口处,主要用来判断信号量的当前可用资源计数是否允许本线程的进入。只有在当前可用资源计数值大于0时,被监视的信号量内核对象才会得到通知。  信号量的使用特点使其更适用于对Socket(套接字)程序中线程的同步。例如,网络上的HTTP服务器要对同一时间内访问同一页面的用户数加以限制,这时可以为没一个用户对服务器的页面请求设置一个线程,而页面则是待保护的共享资源,通过使用信号量对线程的同步作用可以确保在任一时刻无论有多少用户对某一页面进行访问,只有不大于设定的最大用户数目的线程能够进行访问,而其他的访问企图则被挂起,只有在有用户退出对此页面的访问后才有可能进入。下面给出的示例代码即展示了类似的处理过程:// 信号量对象句柄
    HANDLE hSemaphore;
    UINT ThreadProc15(LPVOID pParam)

     // 试图进入信号量关口
     WaitForSingleObject(hSemaphore, INFINITE);
     // 线程任务处理
     AfxMessageBox("线程一正在执行!");
     // 释放信号量计数
     ReleaseSemaphore(hSemaphore, 1, NULL);
     return 0;
    }
    UINT ThreadProc16(LPVOID pParam)

     // 试图进入信号量关口
     WaitForSingleObject(hSemaphore, INFINITE);
     // 线程任务处理
     AfxMessageBox("线程二正在执行!");
     // 释放信号量计数
     ReleaseSemaphore(hSemaphore, 1, NULL);
     return 0;
    }
    UINT ThreadProc17(LPVOID pParam)

     // 试图进入信号量关口
     WaitForSingleObject(hSemaphore, INFINITE);
     // 线程任务处理
     AfxMessageBox("线程三正在执行!");
     // 释放信号量计数
     ReleaseSemaphore(hSemaphore, 1, NULL);
     return 0;
    }
    ……
    void CSample08View::OnSemaphore() 
    {
     // 创建信号量对象
     hSemaphore = CreateSemaphore(NULL, 2, 2, NULL);
     // 开启线程
     AfxBeginThread(ThreadProc15, NULL);
     AfxBeginThread(ThreadProc16, NULL);
     AfxBeginThread(ThreadProc17, NULL);

    图4 开始进入的两个线程
    图5 线程二退出后线程三才得以进入  上述代码在开启线程前首先创建了一个初始计数和最大资源计数均为2的信号量对象hSemaphore。即在同一时刻只允许2个线程进入由hSemaphore保护的共享资源。随后开启的三个线程均试图访问此共享资源,在前两个线程试图访问共享资源时,由于hSemaphore的当前可用资源计数分别为2和1,此时的hSemaphore是可以得到通知的,也就是说位于线程入口处的WaitForSingleObject()将立即返回,而在前两个线程进入到保护区域后,hSemaphore的当前资源计数减少到0,hSemaphore将不再得到通知,WaitForSingleObject()将线程挂起。直到此前进入到保护区的线程退出后才能得以进入。图4和图5为上述代脉的运行结果。从实验结果可以看出,信号量始终保持了同一时刻不超过2个线程的进入。  在MFC中,通过CSemaphore类对信号量作了表述。该类只具有一个构造函数,可以构造一个信号量对象,并对初始资源计数、最大资源计数、对象名和安全属性等进行初始化,其原型如下:CSemaphore( LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName = NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL );   在构造了CSemaphore类对象后,任何一个访问受保护共享资源的线程都必须通过CSemaphore从父类CSyncObject类继承得到的Lock()和UnLock()成员函数来访问或释放CSemaphore对象。与前面介绍的几种通过MFC类保持线程同步的方法类似,通过CSemaphore类也可以将前面的线程同步代码进行改写,这两种使用信号量的线程同步方法无论是在实现原理上还是从实现结果上都是完全一致的。下面给出经MFC改写后的信号量线程同步代码:// MFC信号量类对象
    CSemaphore g_clsSemaphore(2, 2);
    UINT ThreadProc24(LPVOID pParam)

     // 试图进入信号量关口
     g_clsSemaphore.Lock();
     // 线程任务处理
     AfxMessageBox("线程一正在执行!");
     // 释放信号量计数
     g_clsSemaphore.Unlock();
     return 0;
    }
    UINT ThreadProc25(LPVOID pParam)
    {
     // 试图进入信号量关口
     g_clsSemaphore.Lock();
     // 线程任务处理
     AfxMessageBox("线程二正在执行!");
     // 释放信号量计数
     g_clsSemaphore.Unlock();
     return 0;
    }
    UINT ThreadProc26(LPVOID pParam)
    {
     // 试图进入信号量关口
     g_clsSemaphore.Lock();
     // 线程任务处理
     AfxMessageBox("线程三正在执行!");
     // 释放信号量计数
     g_clsSemaphore.Unlock();
     return 0;
    }
    ……
    void CSample08View::OnSemaphoreMfc() 
    {
     // 开启线程
     AfxBeginThread(ThreadProc24, NULL);
     AfxBeginThread(ThreadProc25, NULL);
     AfxBeginThread(ThreadProc26, NULL);