使用互斥同步
在多線程程式中同步通路的另一個方法就是使用互斥,其作用允許程式鎖住一個對象,進而隻有一個線程可以通路他。要控制對臨界區代碼的通路,在我們進入這段代碼之前鎖住一個互斥量,并且在我們完成操作時進行解鎖。
使用互斥所需要基本函數與信号量所需要的函數相似,其聲明如下:
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t
*mutexattr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
如平常一樣,成功時傳回0,如果失敗則會傳回一個錯誤代碼,但是并沒有設定errno;我們必須使用傳回代碼。
與信号量相類似,這些函數以一個指向前面聲明的對象的指針為參數,在互斥方法是一個pthread_mutex_t。額外的屬性參數pthread_mutexattr_t允許我們為互斥提供屬性,這可以控制其行為。屬性類型預設為"fast"。這有一個小的缺點,如果我們的程式試着在一個已經上鎖的互斥量上調用pthread_mutex_lock,程式就會阻塞。因為擁有鎖的線程現在被阻塞了,互斥量就不會被解鎖,進而程式就會進入死鎖狀态。可以修改互斥量的屬性,進而他或者可以檢測這種情況并傳回一個錯誤,或者是循環操作并且在同一個線程上允許多個鎖。
設定互斥量的屬性超出了本書的範圍,是以我們會為屬性指針傳遞NULL并且使用預設行為。我們可以通過閱讀pthread_mutex_init手冊頁了解修改屬性的内容。
試驗--線程互斥
再一次說明,下面的程式是我們原始程式thread1.c的修改版,但是進行了大量的修改。這一次,我們有一些偏狂來通路我們的臨界變量,并且使用一個互斥量來保證每次隻有一個線程通路他們。為了使得代碼更易于閱讀,我們忽略了由互斥量加鎖與解鎖操作傳回值的錯誤檢測。在生産代碼中,我們應該檢測這些傳回值。下面是這個新程式,thread4.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
void *thread_function(void *arg);
pthread_mutex_t work_mutex;
#define WORK_SIZE 1024
char work_area[WORK_SIZE];
int time_to_exit = 0;
int main()
{
int res;
pthread_t a_thread;
void *thread_result;
res = pthread_mutex_init(&work_mutex,NULL);
if(res != 0)
{
perror("Mutex initialization failed");
exit(EXIT_FAILURE);
}
res = pthread_create(&a_thread,NULL,thread_function,NULL);
if(res != 0)
{
perror("Thread creation failed");
exit(EXIT_FAILURE);
}
pthread_mutex_lock(&work_mutex);
printf("Input some text, Enter 'end' to finish/n");
while(!time_to_exit)
{
fgets(work_area,WORK_SIZE,stdin);
pthread_mutex_unlock(&work_mutex);
while(1)
{
pthread_mutex_lock(&work_mutex);
if(work_area[0] != '/0')
{
pthread_mutex_unlock(&work_mutex);
sleep(1);
}
else
{
break;
}
}
}
pthread_mutex_unlock(&work_mutex);
printf("/nWaiting for thread to finish.../n");
res = pthread_join(a_thread,&thread_result);
if(res != 0)
{
perror("Thread join failed");
exit(EXIT_FAILURE);
}
printf("Thread joined/n");
pthread_mutex_destroy(&work_mutex);
exit(EXIT_SUCCESS);
}
void *thread_function(void *arg)
{
sleep(1);
pthread_mutex_lock(&work_mutex);
while(strncmp("end",work_area,3) != 0)
{
printf("You input %d characters/n",strlen(work_area)-1);
work_area[0] = '/0';
pthread_mutex_unlock(&work_mutex);
sleep(1);
pthread_mutex_lock(&work_mutex);
while(work_area[0] == '/0')
{
pthread_mutex_unlock(&work_mutex);
sleep(1);
pthread_mutex_lock(&work_mutex);
}
}
time_to_exit = 1;
work_area[0] = '/0';
pthread_mutex_unlock(&work_mutex);
pthread_exit(0);
}
$ cc -D_REENTRANT -I/usr/include/nptl thread4.c –o thread4 -L/usr/lib/nptl -
lpthread
$ ./thread4
Input some text. Enter ‘end’ to finish
Whit
You input 4 characters
The Crow Road
You input 13 characters
end
Waiting for thread to finish...
Thread joined
工作原理
我們開始聲明了一個互斥量,我們的工作區,而且這次,我們聲明了一個額外變量:time_to_exit。
pthread_mutex_t work_mutex;
#define WORK_SIZE 1024
char work_area[WORK_SIZE];
int time_to_exit = 0;
然後我們初始化互斥量
res = pthread_mutex_init(&work_mutex, NULL);
if (res != 0) {
perror(“Mutex initialization failed”);
exit(EXIT_FAILURE);
}
接下來我們開始我們的新線程。下面是線程函數内部執行的代碼:
pthread_mutex_lock(&work_mutex);
while(strncmp(“end”, work_area, 3) != 0) {
printf(“You input %d characters/n”, strlen(work_area) -1);
work_area[0] = ‘/0’;
pthread_mutex_unlock(&work_mutex);
sleep(1);
pthread_mutex_lock(&work_mutex);
while (work_area[0] == ‘/0’ ) {
pthread_mutex_unlock(&work_mutex);
sleep(1);
pthread_mutex_lock(&work_mutex);
}
}
time_to_exit = 1;
work_area[0] = ‘/0’;
pthread_mutex_unlock(&work_mutex);
首先,新線程嘗試鎖住這個互斥量。如果他已經被鎖住了,調用就會阻塞直到互斥量被釋放。一旦我們通路,我們進行檢測來檢視我們是否正被請求退出。如果我們被請求退出,我們隻是簡單的設定time_to_exit,清除工作區的第一個字元,并且退出。
如果我們不希望退出,我們計算字元數然後清空第一個字元為空。我們使用将第一個字元清空的方法來通知讀取程式我們已經完成了計算。我們然後解鎖互斥量并且等待主線程運作。我們會循環嘗試鎖住這個互斥量,當我們成功時,我們會檢測主線程是否為我們指定了更多的工作要做。如果沒有,我們解鎖互斥量并等待一段更長的時間。如果有,我們計算字元數并且再次進入循環。
下面是主線程:
pthread_mutex_lock(&work_mutex);
printf(“Input some text. Enter ‘end’ to finish/n”);
while(!time_to_exit) {
fgets(work_area, WORK_SIZE, stdin);
pthread_mutex_unlock(&work_mutex);
while(1) {
pthread_mutex_lock(&work_mutex);
if (work_area[0] != ‘/0’) {
pthread_mutex_unlock(&work_mutex);
sleep(1);
}
else {
break;
}
}
}
pthread_mutex_unlock(&work_mutex);
這與上面所說的線程類似。我們鎖住工作區域,進而我們可以向其中讀取文本,然後解鎖允許其他的線程通路來計算單詞數。循環往複,我們重新鎖住互斥量,檢測單詞數是否進行了計算,如果我們需要等待更長的時間,我們就會釋放這個鎖。正如我們在前面所注意到的,這并不是一個好的程式設計習慣,而在真實的世界中,我們可以使用信号量來避免這種情況。然而,這裡的代碼隻是作為一個例子。