Ridiculous的文章

  • 线程同步之临界区

    背景在学校学习《操作系统》这门课程的时候,进程线程的调度同步都是比较重要的知识点。关于线程的同步方法有很多种,例如互斥量、信号量、事件、临界区等。
    本文就从编程实现的角度谈谈编程中临界区在多线程同步中的使用方式,现在,把实现的思路和过程写成文档,分享给大家。
    实现原理有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。
    临界区在使用时以CRITICAL_SECTION结构对象保护共享资源,并分别用WIN32函数EnterCriticalSection 和 LeaveCriticalSection 去进入和离开一个临界区。
    所用到的CRITICAL_SECTION结构对象必须要事先经过 InitializeCriticalSection 的初始化后才能使用,而且必须确保所有线程中的任何试图访问此共享资源的代码都处在此临界区的保护之下。否则临界区将不会起到应有的作用,共享资源依然有被破坏的可能。
    临界区使用完毕之后,要使用 DeleteCriticalSection 函数删除临界区,释放资源。
    编码实现为了更好理解临界区的概念,我们开发了这样一个程序:开启 3 个线程,每个线程都循环打印显示同一个整型全局变量,并将它自增1。要求,每个数字只显示一遍,而且是按 1 递增显示。
    如果我们什么操作不加的话,直接创建 3 个多线程去打印显示的话,代码如下:
    UINT ThreadProc(LPVOID lpVoid){ while (TRUE) { printf("%d\n", g_iCount); g_iCount++; if (g_iCount > iStop) { break; } } return 0;}BOOL CriticalSectionTest(){ // 多线程1 HANDLE hThread1 = ::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, NULL); // 多线程2 HANDLE hThread2 = ::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, NULL); // 多线程3 HANDLE hThread3 = ::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, NULL); // 等待线程结束 ::WaitForSingleObject(hThread1, INFINITE); ::WaitForSingleObject(hThread2, INFINITE); ::WaitForSingleObject(hThread3, INFINITE); // 关闭句柄 ::CloseHandle(hThread1); ::CloseHandle(hThread2); ::CloseHandle(hThread3); return TRUE;}
    那么,上面程序的显示结果如下图所示:

    可以看到,输出的每个数字并不是都唯一,有些都重复输出,而且并不是按递增 1 的规律输出的。原因细想一下应该就明白了,创建了 3 个多线程,有可能会出现当线程1执行完打印输出的时候,线程2也执行到打印输出,这是全局变量g_iCount值还是0,所以便会重复输出。而它们也会同时执行g_iCount++,那么之后的输出就不会按递增1进行输出了。
    所以,为了每次输出和全局变量递增的时候,只能有一个线程去的独占执行,这时就需要临界区的帮助了。
    加入临界区后的代码就变成下面这样子:
    UINT ThreadProc(LPVOID lpVoid){ while (TRUE) { // 进入临界区 ::EnterCriticalSection(&g_cs); printf("%d\n", g_iCount); g_iCount++; if (g_iCount > iStop) { break; } // 离开临界区 ::LeaveCriticalSection(&g_cs); } return 0;}BOOL CriticalSectionTest(){ // 初始化临界区 ::InitializeCriticalSection(&g_cs); // 多线程1 HANDLE hThread1 = ::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, NULL); // 多线程2 HANDLE hThread2 = ::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, NULL); // 多线程3 HANDLE hThread3 = ::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, NULL); // 等待线程结束 ::WaitForSingleObject(hThread1, INFINITE); ::WaitForSingleObject(hThread2, INFINITE); ::WaitForSingleObject(hThread3, INFINITE); // 关闭句柄 ::CloseHandle(hThread1); ::CloseHandle(hThread2); ::CloseHandle(hThread3); // 删除临界区 ::DeleteCriticalSection(&g_cs); return TRUE;}
    代码的执行效果如下所示:

    这时,输出满足要求了,每个数字只显示 1 遍,而且是递增 1 输出。也就说明临界区起到了同步的效果,每次显示和递增,都保证了只能一个线程执行这部分的代码。
    总结在编程开发中,使用临界区之前一定记得要进行初始化操作,使用完毕后,要删除临界区。
    2  留言 2018-11-07 10:59:10

发送私信

难过时,吃一粒糖,告诉自己生活是甜的

10
文章数
13
评论数
eject