多線程的最大優點之一是數據的共享性,各個進程共享父進程處沿襲的數據段,可以方便的獲得、修改數據。但這也給多線程編程帶來了許多問題。 我們必須當心有多個不同的進程訪問相同的變量。許多函數是不可重入的,即同時不能運行一個函數的多個拷貝(除非使用不同的數據段)。 在函數中聲明的靜態變量常常帶來問題,函數的返回值也會有問題。因為如果返回的是函數內部靜態聲明的空間的地址, 則在一個線程調用該函數得到地址后使用該地址指向的數據時,別的線程可能調用此函數并修改了這一段數據。 為了保護變量,我們必須使用信號量、互斥等方法來保證我們對變量的正確使用。在Poxi標準中提供了互斥量,讀寫鎖,條件變量實現互斥訪問,下面意義探討。
假設多個線程向同一個文件寫入數據,若對寫入的順序不加以管理和控制,那么最終生成的文件肯定是無法解析的。所以必須用互斥鎖來保證一段時間內只有一個線程在寫入文件,當一個線程寫入完成后再給下一線程寫入。
鎖可以被動態或靜態創建,可以用宏PTHREAD_MUTEX_INITIALIZER來靜態的初始化鎖,采用這種方式比較容易理解,互斥鎖是pthread_mutex_t的結構體,而這個宏是一個結構常量,如下可以完成靜態的初始化:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;另外鎖可以用pthread_mutex_init函數動態的創建,函數原型如下:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t * attr)NULL參數表明使用默認屬性,通常使用默認屬性。
pthread_mutex_trylock()語義與pthread_mutex_lock()類似,不同的是在鎖已經被占據時返回EBUSY而不是掛起等待。
當鎖沒有被鎖定時??梢酝ㄟ^調用pthread_mutex_destory釋放鎖占用的資源。
多個線程可以同時獲得讀鎖(Reader-Writer lock in read mode),但是只有一個線程能夠獲得寫鎖(Reader-writer lock in write mode)。
讀寫鎖總共有三種狀態: 1. 一個或者多個線程獲得讀鎖,其他線程無法獲得寫鎖 2. 一個線程獲得寫鎖,其他線程無法獲得讀鎖 3. 沒有線程獲得此讀寫鎖
#include <pthread.h>//初始化一個讀寫鎖,pthread_rwlockattr_t通常設為NULLint pthread_rwlock_init( pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr)//銷毀一個讀寫鎖int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);//獲取讀鎖int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);//獲取寫鎖int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);//讀寫解鎖int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);適用的場景為條件滿足立刻被喚醒,否則掛起等待。條件需要被mutex保護。 條件變量類型為pthread_cond_t,必須被初始化為PTHREAD_COND_INITIALIZER,等價于調用pthread_cond_init(…, NULL)
#include <pthread.h>//初始化一個條件變量,和互斥鎖一樣我們可以用它來設置條件變量是進程內可用還是進程間可用,默認值是 PTHREAD_ PROCESS_PRIVATE,即此條件變量被同一進程內的各個線程使用。int pthread_cond_init( pthread_cond_t *restrict cond, const pthread_condxattr_t *restrict attr)//銷毀一個條件變量int pthread_cond_destroy(pthread_cond_t *cond);//等待條件發生int pthread_cond_wait( pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);int pthread_cond_timedwait( pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict timeout);/*pthread_cond_timedwait類似,只是當等待超時的時候返回一個錯誤值ETIMEDOUT。超時的時間用timespec結構指定。struct timespec { time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */};注意timespec的時間是絕對時間而非相對時間,因此需要先調用gettimeofday函數獲得當前時間,再轉換成timespec結構,加上偏移量。*///條件滿足后喚醒等待的線程int pthread_cond_signal(pthread_cond_t *cond);int pthread_cond_broadcast(pthread_cond_t *cond);/*兩者的區別是前者會喚醒單個線程,而后者會喚醒多個線程。*/gcc -g -pthread cond.c -lpthread -o test 運行test后,輸出如下:
counter: 0counter(main): 0counter(decrement): 0 decrement_counter lock! counter(increment): 0 increment_counter lock!counter++(before): 0counter++(after): 1increment_counter will unlock!counter--(before): 1counter--(after): 0decrement_counter will unlock! counter(main): 1是不是覺得上面的輸出有點不可思議?明明decrement_counter lock為什么increment_counter lock還能成功。關鍵在于 pthread_cond_wait(&counter_nonzero, &counter_lock)在等待是,實際上已經將鎖unlock,并將線程掛起在等待隊列,等待喚醒。pthread_cond_signal(&counter_nonzero); 實際上會對對應mutex一個lock操作。
在單線程的程序里,有兩種基本的數據:全局變量和局部變量。但在多線程程序里,還有第三種數據類型:線程數據(TSD: Thread-Specific Data)。
它和全局變量很象,在線程內部,各個函數可以象使用全局變量一樣調用它,但它對線程外部的其它線程是不可見的。例如我們常見的變量errno,它返回標準的出錯信息。它顯然不能是一個局部變量,幾乎每個函數都應該可以調用它;但它又不能是一個全局變量,否則在 A線程里輸出的很可能是B線程的出錯信息。
要實現諸如此類的變量,我們就必須使用線程數據。我們為每個線程數據創建一個鍵,它和這個鍵相關聯,在各個線程里,都使用這個鍵來指代線程數據,但在不同的線程里,這個鍵代表的數據是不同的,在同一個線程里,它代表同樣的數據內容。
/*實現功能,創建5個線程,每個線程將日志記錄到thread%d.log日志文件中*/#include <malloc.h>#include <pthread.h>#include <stdio.h>static pthread_key_t thread_log_key;void write_to_thread_log (const char* message){ //從鍵讀取線程數據 FILE* thread_log = (FILE*) pthread_getspecific (thread_log_key); fprintf (thread_log, "%s/n", message); }void close_thread_log (void* thread_log){ fclose ((FILE*) thread_log);}void* thread_function (void* args){ char thread_log_filename[20]; FILE* thread_log; sprintf (thread_log_filename, "thread%d.log", (int) pthread_self ()); thread_log = fopen (thread_log_filename, "w+"); //為當前線程的鍵指定線程數據 pthread_setspecific (thread_log_key, thread_log); //記錄日志 write_to_thread_log ("Thread starting."); return NULL; }int main (){ int i; pthread_t threads[5]; //創建一個鍵,close_thread_log為線程退出時調用的析構函數 pthread_key_create (&thread_log_key, close_thread_log); for (i = 0; i < 5; ++i) pthread_create (&(threads[i]), NULL, thread_function, NULL); for (i = 0; i < 5; ++i) pthread_join (threads[i], NULL); //銷毀鍵 pthread_key_delete(thread_log_key); return 0;}新聞熱點
疑難解答