现在的位置: 首页 > 综合 > 正文

互斥

2013年04月28日 ⁄ 综合 ⁄ 共 10968字 ⁄ 字号 评论关闭
多個thread可能同時存取的記憶體、變數或函數稱為CriticalSection。CriticalSection 類別 用於在多執行緒環境中保護資源。通常這種要受保護的程式區段稱為 CriticalSection 。為什麼要保護呢?因為在程式裡有可能有兩個 thread (可看成一個小小的function)同時存取一個global variable(全域變數)(或函數),這時後,因為程式的需要,thread 不想被其他程式中斷(不被其他Tread插入影響),所以必須要一口起執行完畢,而該需要一口氣執行完的程式區段 則要設定 CriticalSection,以保護目前執行的thread不被其他thread影響。

Thread的Stack和之前提到之系統Stack的功能一樣,包含函數呼叫時的參數、返回位址,以及區域變數等,都會

甚麼是Critical Section?

  • 是一程式區段, 而這個程式區段必須擁有某共用資源的權限才能執行
  • 你可以放心的執行 Critical Section 的程式碼,絕不會有其他的 thread 同時執行你所在的code
  • 你的 thread 會被 preempt 換其他的thread 執行, 但是想要進入 Critical Section 的thread 是不會被 schedule的
  • 系統不保證進入Critical Section thread 的順序,但OS保證公平對待所有要進入的thread

Windows API 用法:

CRITICAL_SECTION g_FileCriticalSection; //宣告

InitializeCriticalSection(&g_FileCriticalSection);// 初始化,建構之意,程式一開始要做

EnterCriticalSection(&g_FileCriticalSection); // 進入 CriticalSection

Function(); //受保護的程式

LeaveCriticalSection(&g_FileCriticalSection);// 離開 CriticalSection

DeleteCriticalSection(&g_FileCriticalSection);// 解構,程式結束之前要做

  1. // CriticalSection 使用範例 
  2. // 別忘了, 設定 multi-thread 版本的 C/C++ run-time library 
  3. // This example is from http://debut.cis.nctu.edu.tw/~ching/   井民全 
  4. #include "stdafx.h" 
  5. #include <windows.h> 
  6. #include <process.h>        // for _beginthreadex(...) 
  7. #include <tchar.h> 
  8. #include <iostream> 
  9. using namespace std; 
  10. // ============ 設定 Critical Section 變數 ============= 
  11. const int MAX_TIMES = 100
  12. CRITICAL_SECTION g_cs; 
  13. // ============ End =============================== 
  14. int g_nIndex = 0
  15. DWORD g_dwTimes[MAX_TIMES]; 
  16. unsigned int __stdcall FirstThread(void *p); 
  17. unsigned int __stdcall SecondThread(void *p); 
  18. int _tmain(int argc, _TCHAR* argv[]) 
  19.     // Step 1: 對 Critical Section 進行初始化的動作 
  20.     InitializeCriticalSection(&g_cs); 
  21.     HANDLE hThread[2]; 
  22.     // Step 2: 建立 FristThread 
  23.     unsigned int thread1_Id; 
  24.     hThread[0]=(HANDLE)_beginthreadex(NULL,0,FirstThread,NULL,0,&thread1_Id); 
  25.     // Step 3: 建立 SecondThread 
  26.     // hThread[1]=(HANDLE) _beginthread(SecondThread,0,NULL); 
  27.     unsigned int thread2_Id; 
  28.     hThread[1]=(HANDLE)_beginthreadex(NULL,0,SecondThread,NULL,0,&thread2_Id); 
  29.     //MSDN http://msdn2.microsoft.com/en-us/library/kdzttdcb(VS.71).aspx 
  30.     WaitForMultipleObjects(2,hThread,TRUE,INFINITE); 
  31.     //MSDN http://msdn2.microsoft.com/en-us/library/ms687025(VS.85).aspx 
  32.     printf("請按任意鍵離開"); 
  33.     getchar(); 
  34.     // Step 4: 刪除 CriticalSection 
  35.     DeleteCriticalSection (&g_cs); 
  36.     // Step 5: 刪除 thread 的 Handle 
  37.     BOOL bOk=CloseHandle(hThread[0]); 
  38.     bOk=CloseHandle(hThread[1]); 
  39.     return 0
  40. unsigned int __stdcall FirstThread(void *p){ 
  41.     bool bQuit=false; 
  42.     while (!bQuit) { 
  43.         EnterCriticalSection( &g_cs);               // 進入 Critical Section 
  44.         if(g_nIndex < MAX_TIMES){    
  45.             g_dwTimes[g_nIndex] = GetTickCount(); //取得自系統啟動到目前的時間 
  46.             printf("/n=========== FirstThread ===========/n"); 
  47.             printf("目前 g_nIndex= %d, 寫入值為= %d/n",g_nIndex,g_dwTimes[g_nIndex]); 
  48.             g_nIndex++;printf("1號加/n"); 
  49.         }else
  50.             bQuit=TRUE; 
  51.         } 
  52.         LeaveCriticalSection(&g_cs);               // 離開 Critical Section    
  53.     } 
  54.     return 0
  55. unsigned int __stdcall SecondThread(void *p){ 
  56.     bool bQuit=false; 
  57.     while (!bQuit) { 
  58.         EnterCriticalSection(&g_cs);                 // 進入 Critical Section 
  59.         if(g_nIndex < MAX_TIMES){ 
  60.             g_nIndex++;printf("2號加/n"); 
  61.             g_dwTimes[g_nIndex - 1] = GetTickCount(); 
  62.             printf("/n---- > SecondThread /n"); 
  63.             printf("目前 g_nIndex= %d, 寫入值為= %d/n",g_nIndex,g_dwTimes[g_nIndex - 1]); 
  64.         }else
  65.             bQuit=TRUE; 
  66.         } 
  67.         LeaveCriticalSection(&g_cs);                 // 離開 Critical Section 
  68.     } 
  69.     return 0

 

 

第二段

 

 

最近由于使用多线程,不可避免的要用到线程之间的同步,对一些常用的windows 中同步函数和机制有了一些初步的了解,并且写了一些小例子来验证,当然其中难免有错误和疏漏之处,希望高手能给我这个小鸟指出不足之处,非常感谢。

目录
一 临界区
二 互斥体
三 事件
四 信号量
五  附录

一 临界区

临界区的使用在线程同步中应该算是比较简单,说它简单还是说它同后面讲到的其它方法相比更容易理解。举个简单的例子:比如说有一个全局变量(公共资源)两个线程都会对它进行写操作和读操作,如果我们在这里不加以控制,会产生意想不到的结果。假设线程A正在把全局变量加1然后打印在屏幕上,但是这时切换到线程B,线程B又把全局变量加1然后又切换到线程A,这时候线程A打印的结果就不是程序想要的结果,也就产生了错误。解决的办法就是设置一个区域,让线程A在操纵全局变量的时候进行加锁,线程B如果想操纵这个全局变量就要等待线程A释放这个锁,这个也就是临界区的概念。


使用方法:
CRITICAL_SECTION cs;
InitializeCriticalSection(&cs);
EnterCriticalSection(&cs);
...
LeaveCriticalSection(&cs);
DeleteCriticalSection(&cs);
#include "stdafx.h"
#include <windows.h>
#include <process.h>
#include <iostream>
using namespace std;

/****************************************************************
*在使用临界区的时候要注意,每一个共享资源就有一个CRITICAL_SECTION
*如果要一次访问多个共享变量,各个线程要保证访问的顺序一致,如果不
*一致,很可能发生死锁。例如:
*   thread one:
*   EnterCriticalSection(&c1)
*   EnterCriticalSection(&c2)
*   ...
*   Leave...
*   Leave...
*
*   thread two:
*   EnterCriticalSection(&c2);
*   EnterCriticalSection(&c1);
*   ...
*   Leave...
*   Leave...
*这样的情况就会发生死锁,应该让线程2进入临界区的顺序同线程1相同
****************************************************************/

const int MAX_THREADNUMS = 4;            //产生线程数目
CRITICAL_SECTION cs;                             //临界区
HANDLE event[MAX_THREADNUMS];       //保存createevent的返回handle
int critical_value = 0;                                   //共享资源

UINT WINAPI ThreadFunc(void* arg)
{
    int thread = (int)arg;
    for (int i = 0; i < 5; i++)
    {   
        EnterCriticalSection(&cs);
        cout << "thread " << thread << " ";

        critical_value++;
        cout << "critical_value = " << critical_value << endl;    
        LeaveCriticalSection(&cs);
    }
    SetEvent(event[thread]);
    return 1;
}

int main(int argc, char* argv[])
{
    cout << "this is a critical_section test program" << endl;
    HANDLE hThread;
    UINT uThreadID;
    DWORD dwWaitRet = 0;
 
    InitializeCriticalSection(&cs);

    for (int i = 0; i < MAX_THREADNUMS; i++)
    {
        event[i] = CreateEvent(NULL, TRUE, FALSE, "");
        if (event[i] == NULL)
        {
            cout << "create event " << i << " failed with code: "
                << GetLastError() << endl;
            continue;
        }
        hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc,
            (void*)i, 0, &uThreadID);
        if (hThread == 0)
        {
            cout << "begin thread " << i << " failed with code: "
                << GetLastError() << endl;
            continue;
        }
        CloseHandle(hThread);
    }
    //等待所有线程完成
    dwWaitRet = WaitForMultipleObjects(MAX_THREADNUMS, event, TRUE, INFINITE);
    switch(dwWaitRet)
    {
    case WAIT_OBJECT_0:             

        cout << "all the sub thread has exit!" << endl;
        break;
    default:
        cout << "wait for all the thread failed with code:" << GetLastError() << endl;
        break;
    }
   
    DeleteCriticalSection(&cs);
    for (int k = 0; k < MAX_THREADNUMS; k++)
    {
        CloseHandle(event[k]);
    }
 return 0;
}

二 互斥体
windows api中提供了一个互斥体,功能上要比临界区强大。也许你要问,这个东东和临界区有什么区别
,为什么强大?它们有以下几点不一致:
1.critical section是局部对象,而mutex是核心对象。因此像waitforsingleobject是不可以等待临界区的。
2.critical section是快速高效的,而mutex同其相比要慢很多
3.critical section使用范围是单一进程中的各个线程,而mutex由于可以有一个名字,因此它是可以应用
于不同的进程,当然也可以应用于同一个进程中的不同线程。
4.critical section 无法检测到是否被某一个线程释放,而mutex在某一个线程结束之后会产生一
个abandoned的信息。同时mutex只能被拥有它的线程释放。下面举两个应用mutex的例子,一个是程序只能运行一个实例,也就是说同一个程序如果已经运行了,就不能再运行了;另一个是关于非常经典的哲学家吃饭问题的例子。

程序运行单个实例:
#include "stdafx.h"
#include <windows.h>
#include <process.h>
#include <iostream>
using namespace std;

//当输入s或者c时候结束程序
void PrintInfo(HANDLE& h, char t)
{
    char c;
    while (1)
    {
        cin >> c;
        if (c == t)
        {
            ReleaseMutex(h);
            CloseHandle(h);
            break;
        }
        Sleep(100);
    }
}
int main(int argc, char* argv[])
{
    //创建mutex,当已经程序发现已经有这个mutex时候,就相当于openmutex
    HANDLE hHandle = CreateMutex(NULL, FALSE, "mutex_test");
    if (GetLastError() == ERROR_ALREADY_EXISTS)
    {
        cout << "you had run this program!" << endl;
        cout << "input c to close this window" << endl;
        PrintInfo(hHandle, 'c');
        return 1;
    }
    cout << "program run!" << endl;
    cout << "input s to exit program" <<endl;
   
    PrintInfo(hHandle, 's');
    return 1;
}

哲学家吃饭问题:

const int PHILOSOPHERS = 5;          //哲学家人数
const int TIME_EATING = 50;         //吃饭需要的时间 毫秒
HANDLE event[PHILOSOPHERS];    //主线程同工作线程保持同步的句柄数组
HANDLE mutex[PHILOSOPHERS];   //mutex数组,这里相当于公共资源筷子
CRITICAL_SECTION cs;                //控制打印的临界区变量

UINT WINAPI ThreadFunc(void* arg)
{
    int num = (int)arg;
   
    DWORD ret = 0;
    while (1)
    {
        ret = WaitForMultipleObjects(2, mutex, TRUE, 1000);
        if (ret == WAIT_TIMEOUT)
        {
            Sleep(100);
            continue;
        }
        EnterCriticalSection(&cs);
            cout << "philosopher " << num << " eatting" << endl;
        LeaveCriticalSection(&cs);
        Sleep(TIME_EATING);
        break;
    }
    //设置时间为有信号
    SetEvent(event[num]);
    return 1;
}
int main(int argc, char* argv[])
{
    HANDLE hThread;
    InitializeCriticalSection(&cs);
    //循环建立线程
    for (int i = 0; i < PHILOSOPHERS; i++)
    {
        mutex[i] = CreateMutex(NULL, FALSE, "");
        event[i] = CreateEvent(NULL, TRUE, FALSE, "");
        hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, (void*)i, 0, NULL);
        if (hThread == 0)
        {
            cout << "create thread " << i << "failed with code: "
                << GetLastError() << endl;
            DeleteCriticalSection(&cs);
            return -1;
        }
        CloseHandle(hThread);
    }
   
    //等待所有的哲学家吃饭结束
    DWORD ret = WaitForMultipleObjects(PHILOSOPHERS, event, TRUE, INFINITE);
    if (ret == WAIT_OBJECT_0)
    {
        cout << "all the philosophers had a dinner!" << endl;
    }
    else
    {
        cout << "WaitForMultipleObjects failed with code: " << GetLastError() << endl;
    }
    DeleteCriticalSection(&cs);
    for (int j = 0; j < PHILOSOPHERS; j++)
    {
        CloseHandle(mutex[j]);
    }
    return 1;
}

三 事件
事件对象的特点是它可以应用在重叠I/O(overlapped I/0)上,比如说socket编程中有两种模型,一种是
重叠I/0,一种是完成端口都是可以使用事件同步。它也是核心对象,因此可以被waitforsingleobje这些函数等待;事件可以有名字,因此可以被其他进程开启。我在前几个例子当中其实已经使用到event了,在这里就不多说了,可以参考前一个例子。

四 信号量
semaphore的概念理解起来可能要比mutex还难, 我先简单说一下创建信号量的函数,因为我在开始使
用的时候没有很快弄清楚,可能现在还有理解不对的地方,如果有错误还是请大侠多多指教。
CreateSemaphore(
  LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,  // SD
  LONG lInitialCount,                                               // initial count
  LONG lMaximumCount,                                            // maximum count
  LPCTSTR lpName                                                   // object name
)
第一个参数是安全性,可以使用默认的安全性选项NULL;第二个和第三个参数是两个long型的数值,
它们表示什么含义呢?lMaxinumCount表示信号量的最大值,必须要大于零。比如是5就表示可以有5个进程或者线程使用,如果第六个进程或者线程想使用的话就必须进入等待队列等待有进程或者线程释放资源。lInitalCount表示信号量的初始值,应该大于或者等于零小于等于lMaximumCount。如果lInitialCount = 0 && lMaximumCount == 5,那么就表示当前资源已经全部被使用,如果再有进程或者线程想使用的话,信号量就会变成-1,该进程或者线程进入等待队列,直到有进程或者线程执行ReleaseMutex;如果lInitialCount = 5 && lMaximumCount == 5,那么就表示现在信号量可以被进程或者线程使用5次,再之后就要进行等待;如果InitialCount = 2 && MaximumCount == 5这样的用法不太常见,表示还可以调用两次CreateSemaphore或者OpenSemaphore,再调用的话就要进入等待状态。最后一个参数表示这个信号量的名字,这样就可以跨进程的时候通过这个名字OpenSemaphore。说了这么多了,不知道说明白没有


看个例子,popo现在好像在本机只能运行三个实例,我们在前面说的mutex可以让程序只是运行一个实
例,下面我通过信号量机制让程序像popo一样运行三个实例。

#include "stdafx.h"
#include <windows.h>
#include <iostream>
using namespace std;

const int MAX_RUNNUM = 3;  //最多运行实例个数
void PrintInfo()
{
    char c;
    cout << "run program" << endl;
    cout << "input s to exit program!" << endl;
    while (1)
    {
        cin >> c;
        if (c == 's')
        {
            break;
        }
        Sleep(10);
    }
}
int main(int argc, char* argv[])
{
   
    HANDLE hSe = CreateSemaphore(NULL, MAX_RUNNUM, MAX_RUNNUM, "semaphore_test");
    DWORD ret = 0;
   
    if (hSe == NULL)
    {
        cout << "createsemaphore failed with code: " << GetLastError() << endl;
        return -1;
    }
  
   
    ret = WaitForSingleObject(hSe, 1000);
    if (ret == WAIT_TIMEOUT)
    {
        cout << "you have runned " << MAX_RUNNUM << " program!" << endl;
        ret = WaitForSingleObject(hSe, INFINITE); 
    }
   
    PrintInfo();
    ReleaseSemaphore(hSe, 1, NULL);
    CloseHandle(hSe);
 return 0;
}

抱歉!评论已关闭.