C++多線程,互斥,同步

同步和互斥

當有多個線程的時候,常常須要去同步這些線程以訪問同一個數據或資源。例如,假設有一個程序,其中一個線程用於把文件讀到內存,而另外一個線程用於統計文件中的字符數。固然,在把整個文件調入內存以前,統計它的計數是沒有意義的。可是,因爲每一個操做都有本身的線程,操做系統會把兩個線程看成是互不相干的任務分別執行,這樣就可能在沒有把整個文件裝入內存時統計字數。爲解決此問題,你必須使兩個線程同步工做。html

所謂同步,是指在不一樣進程之間的若干程序片段,它們的運行必須嚴格按照規定的某種前後次序來運行,這種前後次序依賴於要完成的特定的任務。若是用對資源的訪問來定義的話,同步是指在互斥的基礎上(大多數狀況),經過其它機制實現訪問者對資源的有序訪問。在大多數狀況下,同步已經實現了互斥,特別是全部寫入資源的狀況一定是互斥的。少數狀況是指能夠容許多個訪問者同時訪問資源。linux

所謂互斥,是指散佈在不一樣進程之間的若干程序片段,當某個進程運行其中一個程序片斷時,其它進程就不能運行它們之中的任一程序片斷,只能等到該進程運行完這個程序片斷後才能夠運行。若是用對資源的訪問來定義的話,互斥某一資源同時只容許一個訪問者對其進行訪問,具備惟一性和排它性。但互斥沒法限制訪問者對資源的訪問順序,即訪問是無序的。ios

多線程同步和互斥有幾種實現方法

線程間的同步方法大致可分爲兩類:用戶模式和內核模式。顧名思義,內核模式就是指利用系統內核對象的單一性來進行同步,使用時須要切換內核態與用戶態,而用戶模式就是不須要切換到內核態,只在用戶態完成操做。編程

用戶模式下的方法有:原子操做(例如一個單一的全局變量),臨界區windows

內核模式下的方法有:事件,信號量,互斥量安全

一、臨界區:經過對多線程的串行化來訪問公共資源或一段代碼,速度快,適合控制數據訪問。
二、互斥量:爲協調共同對一個共享資源的單獨訪問而設計的。
三、信號量:爲控制一個具備有限數量用戶資源而設計。
四、事 件:用來通知線程有一些事件已發生,從而啓動後繼任務的開始。網絡

進程間通訊方式

(1)管道(pipe)及有名管道(named pipe):管道可用於具備親緣關係的父子進程間的通訊,有名管道除了具備管道所具備的功能外,它還容許無親緣關係進程間的通訊。多線程

(2)信號(signal):信號是在軟件層次上對中斷機制的一種模擬,它是比較複雜的通訊方式,用於通知進程有某事件發生,一個進程收到一個信號與處理器收到一箇中斷請求效果上能夠說是一致的。socket

(3)消息隊列(message queue):消息隊列是消息的連接表,它克服了上兩種通訊方式中信號量有限的缺點,具備寫權限得進程能夠按照必定得規則向消息隊列中添加新信息;對消息隊列有讀權限得進程則能夠從消息隊列中讀取信息。ide

(4)共享內存(shared memory):能夠說這是最有用的進程間通訊方式。它使得多個進程能夠訪問同一塊內存空間,不一樣進程能夠及時看到對方進程中對共享內存中數據得更新。這種方式須要依靠某種同步操做,如互斥鎖和信號量等。

(5)信號量(semaphore):主要做爲進程之間及同一種進程的不一樣線程之間得同步和互斥手段。

(6)套接字(socket):這是一種更爲通常得進程間通訊機制,它可用於網絡中不一樣機器之間的進程間通訊,應用很是普遍。

C++多線程幾種實現

windows下經過CreateThread  

頭文件 <windows.h>

建立線程API:

 1 HANDLE CreateThread(
 2     __in   SEC_ATTRS
 3     SecurityAttributes,    //線程安全屬性
 4     __in   ULONG
 5     StackSize,        // 堆棧大小 
 6     __in   SEC_THREAD_START
 7     StartFunction,    // 線程函數
 8     __in   PVOID
 9     ThreadParameter,  // 線程參數
10     __in   ULONG
11     CreationFlags,    // 線程建立屬性
12     __out  PULONG
13     ThreadId          // 線程ID
14     );

使用步驟:

HANDLE hThread = CreateThread(NULL, 0, Fun, NULL, 0, NULL);
CloseHandle(hThread);

互斥API:互斥量實現

 1 HANDLE CreateMutex(
 2     LPSECURITY_ATTRIBUTES lpMutexAttributes,
 3     BOOL bInitialOwner,                       // 指定該資源初始是否歸屬建立它的進程
 4     LPCTSTR lpName                            // 指定資源的名稱      
 5     );
 6 
 7 BOOL ReleaseMutex(
 8     HANDLE hMutex   // 該函數用於釋放一個獨佔資源,進程一旦釋放該資源,該資源就再也不屬於它了
 9     );
10 
11 DWORD WaitForSingleObject(
12     HANDLE hHandle,        // 指定所申請的資源的句柄
13     DWORD dwMilliseconds   // 通常指定爲INFINITE,表示若是沒有申請到資源就一直等待該資源
14     );

互斥API:臨界區實現

1 CRITICAL_SECTION cs;
2 InitializeCriticalSection(&cs);
3 EnterCriticalSection(&cs);
4 LeaveCriticalSection(&cs);
5 DeleteCriticalSection(&cs);

 

例子:

 1 #include <iostream>   
 2 #include <windows.h>   
 3 using namespace std;
 4 
 5 HANDLE hMutex;
 6 
 7 DWORD WINAPI Fun(LPVOID lpParamter)
 8 {
 9     while (1) {
10         WaitForSingleObject(hMutex, INFINITE);
11         cout << "Fun display!" << endl;
12         Sleep(1000);
13         ReleaseMutex(hMutex);
14     }
15 }
16 
17 int main()
18 {
19     HANDLE hThread = CreateThread(NULL, 0, Fun, NULL, 0, NULL);
20     hMutex = CreateMutex(NULL, FALSE, "screen");
21     CloseHandle(hThread);
22     while (1) {
23         WaitForSingleObject(hMutex, INFINITE);
24         cout << "main display!" << endl;
25         Sleep(2000);
26         ReleaseMutex(hMutex);
27     }
28 
29     return 0;
30 }

參見:C++多線程編程入門之經典實例

linux下則pthread庫(POSIX)

頭文件<phread.h>

建立線程API:

 1 int pthread_create(pthread_t *tidp, //第一個參數爲指向線程標識符的指針。
 2     const pthread_attr_t *attr,    //第二個參數用來設置線程屬性。
 3     (void*)(*start_rtn)(void*), //第三個參數是線程運行函數的起始地址。
 4     void *arg    //最後一個參數是運行函數的參數
 5     );
 6 
 7 //線程經過調用pthread_exit函數終止執行
 8 void pthread_exit(void* retval);
 9 
10 //函數pthread_join用來等待一個線程的結束,線程間同步的操做,阻塞函數
11 int pthread_join(pthread_t thread, void **retval);

例子:

 1 #include <stdio.h>
 2 #include <pthread.h>
 3 #include <sched.h>
 4 
 5 static int run = 1;
 6 static int retvalue;
 7 
 8 void *start_routine(void *arg)
 9 {
10     int *running = arg;
11     printf("子線程初始化完畢,傳入參數爲:%d\n", *running);
12     while (*running)
13     {
14         printf("子線程正在運行\n");
15         usleep(1);
16     }
17     printf("子線程退出\n");
18 
19     retvalue = 8;
20     pthread_exit((void*)&retvalue);
21 }
22 
23 int main(void)
24 {
25     pthread_t pt;
26     int ret = -1;
27     int times = 3;
28     int i = 0;
29     int *ret_join = NULL;
30 
31     ret = pthread_create(&pt, NULL, (void*)start_routine, &run);
32     if (ret != 0)
33     {
34         printf("創建線程失敗\n");
35         return 1;
36     }
37     usleep(1);
38     for (; i < times; i++)
39     {
40         printf("主線程打印\n");
41         usleep(1);
42     }
43     run = 0;
44     pthread_join(pt, (void*)&ret_join);
45     printf("線程返回值爲:%d\n", *ret_join);
46     return 0;
47 
48 }

互斥步驟:

互斥鎖:等價於二元信號量

  • pthread_mutex_init初始化一個鎖
  • pthread_mutex_destory銷燬互斥鎖
  • pthread_mutex_lock原子方式給加鎖,若是自身線程對自身加鎖的鎖再次加鎖,則死鎖;其他線程加鎖則不會死鎖,但會阻塞。
  • pthread_mutex_trylock非阻塞加鎖
  • pthread_mutex_unlock原子操做方式來解鎖

注意:鎖分爲遞歸鎖和非遞歸鎖,遞歸鎖能夠對自身加鎖的鎖加鎖屢次。

pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
pthread_mutex_lock(&mutex);
pthread_mutex_unlock(&mutex);

生產者消費者例子:(僅僅互斥,沒有用信號量同步)

 1 /*
 2 * ex04-mutex.c
 3 * 線程實例
 4 */
 5 #include <stdio.h>
 6 #include <pthread.h>
 7 #include <sched.h>
 8 
 9 
10 void *producter_f (void *arg);
11 void *consumer_f (void *arg);
12 
13 
14 int buffer_has_item=0;
15 pthread_mutex_t mutex;
16 
17 int running =1 ;
18 
19 int main (void)
20 {
21     pthread_t consumer_t;
22     pthread_t producter_t;
23     
24     pthread_mutex_init (&mutex,NULL);
25     
26     pthread_create(&producter_t, NULL,(void*)producter_f, NULL );
27     pthread_create(&consumer_t, NULL, (void *)consumer_f, NULL);
28     usleep(1);
29     running =0;
30     pthread_join(consumer_t,NULL);
31     pthread_join(producter_t,NULL);
32     pthread_mutex_destroy(&mutex);
33     
34     return 0;
35 }
36 
37 void *producter_f (void *arg)
38 {
39     while(running)
40     {
41         pthread_mutex_lock (&mutex);
42         buffer_has_item++;
43         printf("生產,總數量:%d\n",buffer_has_item);
44         pthread_mutex_unlock(&mutex);
45     }
46 }
47 
48 void *consumer_f(void *arg)
49 {
50     while(running)
51     {
52         pthread_mutex_lock(&mutex);
53         buffer_has_item--;    
54         printf("消費,總數量:%d\n",buffer_has_item);
55         pthread_mutex_unlock(&mutex);
56     }
57 }
View Code

同步步驟:信號量 頭文件<semaphore.h>

  • sem_init函數用於初始化一個未命名的信號量
  • sem_destory函數用於銷燬信號量
  • sem_wait函數以原子操做的方式將信號量的值減1,若是信號量的值爲0,則阻塞。
  • sem_trywait上面的非阻塞版本,若是信號量爲0,當即返回-1,並設置errno爲EAGIN
  • sem_post將信號量的值加1

參見《Linux網絡編程》

C11std::thread

參見:C11多線程

三方庫:如boost的thread

相關文章
相關標籤/搜索