同步和互斥
當有多個線程的時候,經常需要去同步這些線程以訪問同一個數據或資源。例如,假設有一個程序,其中一個線程用於把文件讀到內存,而另一個線程用於統計文件中的字符數。當然,在把整個文件調入內存之前,統計它的計數是沒有意義的。但是,由於每個操作都有自己的線程,操作系統會把兩個線程當作是互不相干的任務分別執行,這樣就可能在沒有把整個文件裝入內存時統計字數。為解決此問題,你必須使兩個線程同步工作。
所謂同步,是指在不同進程之間的若干程序片斷,它們的運行必須嚴格按照規定的某種先后次序來運行,這種先后次序依賴於要完成的特定的任務。如果用對資源的訪問來定義的話,同步是指在互斥的基礎上(大多數情況),通過其它機制實現訪問者對資源的有序訪問。在大多數情況下,同步已經實現了互斥,特別是所有寫入資源的情況必定是互斥的。少數情況是指可以允許多個訪問者同時訪問資源。
所謂互斥,是指散布在不同進程之間的若干程序片斷,當某個進程運行其中一個程序片段時,其它進程就不能運行它們之中的任一程序片段,只能等到該進程運行完這個程序片段后才可以運行。如果用對資源的訪問來定義的話,互斥某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無序的。
多線程同步和互斥有幾種實現方法
線程間的同步方法大體可分為兩類:用戶模式和內核模式。顧名思義,內核模式就是指利用系統內核對象的單一性來進行同步,使用時需要切換內核態與用戶態,而用戶模式就是不需要切換到內核態,只在用戶態完成操作。
用戶模式下的方法有:原子操作(例如一個單一的全局變量),臨界區。
內核模式下的方法有:事件,信號量,互斥量。
1、臨界區:通過對多線程的串行化來訪問公共資源或一段代碼,速度快,適合控制數據訪問。
2、互斥量:為協調共同對一個共享資源的單獨訪問而設計的。
3、信號量:為控制一個具有有限數量用戶資源而設計。
4、事 件:用來通知線程有一些事件已發生,從而啟動后繼任務的開始。
進程間通信方式
(1)管道(pipe)及有名管道(named pipe):管道可用於具有親緣關系的父子進程間的通信,有名管道除了具有管道所具有的功能外,它還允許無親緣關系進程間的通信。
(2)信號(signal):信號是在軟件層次上對中斷機制的一種模擬,它是比較復雜的通信方式,用於通知進程有某事件發生,一個進程收到一個信號與處理器收到一個中斷請求效果上可以說是一致的。
(3)消息隊列(message queue):消息隊列是消息的鏈接表,它克服了上兩種通信方式中信號量有限的缺點,具有寫權限得進程可以按照一定得規則向消息隊列中添加新信息;對消息隊列有讀權限得進程則可以從消息隊列中讀取信息。
(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 }
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 }
同步步驟:信號量 頭文件<semaphore.h>
- sem_init函數用於初始化一個未命名的信號量
- sem_destory函數用於銷毀信號量
- sem_wait函數以原子操作的方式將信號量的值減1,如果信號量的值為0,則阻塞。
- sem_trywait上面的非阻塞版本,如果信號量為0,立即返回-1,並設置errno為EAGIN
- sem_post將信號量的值加1
參見《Linux網絡編程》
C11std::thread
參見:C11多線程