1.線程同步
POSIX支持用於短期鎖定的互斥鎖以及可以等待無限期限的條件變量。
在線程化程序中進行信號處理格外複雜,但是用專用線程來取代信號處理程序,可以降低其複雜性。
學習目標:互斥鎖、條件變量、讀--寫鎖、經典同步問題、帶信號的線程
1.1POSIX同步函數
描 述 POSIX 函數
互斥鎖
pthread_mutex_t pthread_mutex_destroy
pthread_mutex_init
pthread_mutex_lock
pthread_mutex_trylock
pthread_mutex_unlock
條件變量 pthread_cond_destroy
pthread_cond_init
pthread_cond_broadcast
pthread_cond_signal
pthread_cond_timewait
pthread_cond_wait
讀--寫鎖 pthread_rwlock_destroy
pthread_rwlock_init
pthread_rwlock_rdlock
pthread_rwlock_wrlock
pthread_rwlock_timedrdlock
pthread_rwlock_timewrlock
pthread_rwlock_tryrdlock
pthread_rwlock_trywrlock
每種同步機制都提供了一個初始化函數和一個銷燬對象的函數;
互斥鎖和條件變量可以進行靜態初始化;
每種同步機制都有與之相應的屬性對象,也可使用默認的屬性對象。
1.2互斥鎖(或互斥量)
互斥量可以處於鎖定狀態,也可以處於解鎖狀態。
互斥量有一個等待該互斥量的線程隊列,互斥量的等待隊列中的線程順序由調度策略確定。
互斥量只能短時間持有,等待輸入這樣的持續時間不確定的情況,用條件變量來同步。
互斥函數不是線程取消點,也不能被信號函數中斷,除非線程終止或異步取消了線程。
1.2.1創建並初始化一個互斥量
POSIX用pthread_mutex_t類型表示互斥鎖,程序在使用其同步之前必須對其初始化。
如果線程試圖初始化一個已經被初始化的互斥量,POSIX中明確指出,該行爲是爲定義的,必須避免出現這種情況。
對靜態分配的pthread_mutex_t只要將PTHREAD_MUTEX_INITIALIZER賦給變量就行了。
例:
pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;
靜態初始化程序比pthread_mutex_init更有效。
對動態分配或者沒有默認互斥屬性的pthread_mutex_t,要調用pthread_mutex_init初始化
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
attr爲NULL則初始化一個帶默認屬性的互斥量。
成功返回0,不成功返回非零的錯誤碼:
EAGAIN 系統缺乏初始化*mutex所需的非內存資源
ENOMEN 系統缺乏初始化*mutex所需的內存資源
EPERM 調用程序沒有適當的優先級
例:
int error;
pthread_mutex_t mylock;
if ( (error = pthread_mutex_init(&mylock, NULL)) != 0 )
{
fprintf (stderr, “Failed to initialize mylock : %s/n”, strerror(error) );
exit(1);
}
1.2.2銷燬一個互斥量
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
成功返回0,不成功返回非零的錯誤碼,沒有定義必須檢測的錯誤。
例:
int error
pthread_mutex_t mylock;
if ( (error = pthread_mutex_destroy(&mylock)) != 0 )
{
fprintf (stderr, “Failed to destroy mylock : %s/n”, strerror(error));
exit(1);
}
pthread_mutex_init可以對已經被pthread_mutex_destroy銷燬的變量進行重新初始化。
POSIX明確說明,以下兩種情況的行爲是爲定義的,須避免:
線程引用已經銷燬的互斥量;一個線程調用了pthread_mutex_destroy,而另一個將互斥量鎖定。
1.2.3互斥量的鎖定和解鎖
#include <pthread.h>
int pthread_mutex_lock( pthread_mutex_t *mutex);
int pthread_mutex_trylock( pthread_mutex_t *mutex);
int ptrehad_mutex_unlock( pthread_mtex_t *mutex);
成功返回0,不成功返回非零的錯誤碼,這三個函數必須檢測相應的錯誤,以下是錯誤碼:
EINVAL 互斥量具有協議屬性PTHREAD_PRIO_PROTECT(該屬性可以防止排序優先級反轉的情況),而調用程序的優先級比互斥量當前的優先級的上限還要高(pthread_mutex_lock pthread_mutex_trylock)。
EBUSY 另一個線程持有鎖(pthread_mutex_trylock)。
例:
pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mylock);
/* critical section */
pthread_mutex_unlock(&mylock);
//代碼省略了錯誤檢測
1.2.4用互斥量保護不安全的庫函數
可以使用互斥量保護不安全的庫函數,POSIX標準將C庫中的rand函數列爲多線程化應用程序不安全的函數,如果能確保不會有兩個函數同時調用它,就可以在一個多線程化的環境中使用rand函數。以下提供安全的例程
例:
#include <pthread.h>
#include <stdlib.h>
int randsafe(double *ranp)
{
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int error;
if ( (error = pthread_mutex_lock(&lock)) != 0 )
{
return error;
}
*ranp = ( rand() + 0.5 )/(RAND_MAX + 1.0 );
return pthread_mutex_unlock(&lock);
}
1.2.5用互斥量對標誌符和全局值同步
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <stdio.h>
#include <unistd.h>
#define TEN_MILLION 1000000L
static int doneflag = 0;
static pthread_mutex_t donelock = PTHREAD_MUTEX_INITIALIZER;
static int globalerror = 0;
static pthread_mutex_t errorlock = PTHREAD_MUTEX_INITIALIZER;
static int count = 0;
static double sum = 0.0;
static pthread_mutex_t sumlock = PTHREAD_MUTEX_INITIALIZER;
int randsafe(double *ranp)
{
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int error;
if ( (error = pthread_mutex_lock(&lock)) != 0 )
{
return error;
}
*ranp = (rand() + 0.5)/(RAND_MAX + 1.0);
return pthread_mutex_unlock(&lock);
}
int getdone(int *flag)
{
int error;
if ( (error = pthread_mutex_lock(&donelock)) != 0 )
{
return error;
}
*flag = doneflag;
return pthread_mutex_unlock(&donelock);
}
int setdone(void)
{
int error;
if ( (error = pthread_mutex_lock(&donelock)) != 0 )
{
return error;
}
doneflag = 1;
return pthread_mutex_unlock(&donelock);
}
int geterror(int *error)
{
int terror;
if ( (terror = pthread_mutex_lock(&errorlock)) != 0 )
{
return terror;
}
*error = globalerror;
return pthread_mutex_unlock(&errorlock);
}
int seterror(int error)
{
int terror;
if (error == 0)
{
return error;
}
if ( (terror = pthread_mutex_lock(&errorlock)) != 0 )
{
return terror;
}
if ( globalerror == 0 )
{
globalerror = error;
}
terror = pthread_mutex_unlock(&errorlock);
return terror>0 ? terror:error;
}
int add(double x)
{
int error;
if ( (error = pthread_mutex_lock(&sumlock)) != 0 )
{
return seterror(error);
}
sum += x;
count ++;
error = pthread_mutex_unlock(&sumlock);
return seterror(error);
}
int getsum(double *sump)
{
int error;
if ( (error = pthread_mutex_lock(&sumlock)) != 0 )
{
return seterror(error);
}
*sump = sum;
error = pthread_mutex_unlock(&sumlock);
return seterror(error);
}
int getcountandsum(int *countp, double *sump)
{
int error;
if ( (error = pthread_mutex_lock(&sumlock)) != 0 )
{
return seterror(error);
}
*countp = count;
*sump = sum;
error = pthread_mutex_unlock(&sumlock);
return seterror(error);
}
int showresults(void);
void *computethread(void *arg1)
{
int error;
int localdone = 0;
struct timespec sleeptime;
double val;
sleeptime.tv_sec = 0;
sleeptime.tv_nsec = TEN_MILLION;
while (localdone == 0)
{
if ( (error = randsafe(&val)) != 0 )
{
break;
}
if ( (error = add(sin(val))) != 0 )
{
break;
}
if ( (error = getdone(&localdone)) != 0 )
{
break;
}
nanosleep(&sleeptime, NULL);
}
seterror(error);
return NULL;
}
int main(int argc, char *argv[])
{
int error;
int i;
int numthreads;
int sleeptime;
pthread_t *tids;
if (argc != 3)
{
fprintf (stderr, "Usage: %s numthreads sleeptimes/n", argv[0]);
exit(1);
}
numthreads = atoi (argv[1]);
sleeptime = atoi (argv[2]);
if ( (tids = (pthread_t *)calloc (numthreads, sizeof(pthread_t))) == NULL )
{
perror("Failed to allocate space for thead IDs");
exit(2);
}
for (i=0; i<numthreads; i++)
{
if ( (error = pthread_create (tids+i, NULL, computethread, NULL)) != 0)
{
fprintf (stderr, "Failedt to start thread %d:%s/n", i, strerror(error));
exit(3);
}
}
sleep (sleeptime);
if ( (error = setdone()) != 0 )
{
fprintf (stderr, "Failed to set done:%s/n", strerror(error));
exit(4);
}
for (i=0; i<numthreads; i++)
{
if ( (error = pthread_join(tids[i], NULL)) != 0 )
{
fprintf (stderr, "Failed to join thread %d:%s/n", i, strerror(error));
exit(5);
}
}
if ( showresults() != 0 )
{
exit(6);
}
exit(0);
}
int showresults(void)
{
double average;
double calculated;
int count;
double err;
int error;
int gerror;
double perr;
double sum;
if ( ((error = getcountandsum(&count, &sum)) != 0)
|| ((error = geterror(&gerror)) != 0) )
{
fprintf (stderr, "Failed to get results: %s/n", strerror(error));
return -1;
}
if (gerror != 0)
{
fprintf (stderr, "Failet to compute sum:%s/n", strerror(gerror));
return -1;
}
if (count == 0)
{
printf ("NO value were summed./n");
}
else
{
calculated = 1.0 -cos(1.0);
average = sum/count;
err = average - calculated;
perr = 100.0*err/calculated;
printf ("The sum is %f and the count is %d/n", sum, count);
printf ("The average is %f and error is %f or %f%%/n", average, err, perr);
}
return 0;
}
./mutex 2 1
The sum is 121.423602 and the count is 254
The average is 0.478046 and error is 0.018348 or 3.991315%
1.2.6用互斥量使數據結構成爲線程安全的
線程化程序中大多數共享的數據結構都必須由同步機制保護,以確保能得到正確的結果。
實現只需要將每個函數都包裝在一對互斥調用中。
1.3最多一次和最少一次的執行
最多一次和最少一次,對初始化來說非常重要。換句話說,初始化的工作正好執行一次。有時程序結構保證最少執行一次,這是時就需要結合最多一次的策略來保證正好執行一次。
以下討論最多執行一次常用的策略:
單次初始化的概念非常重要,例如爲一個已經初始化的互斥量調用pthread_mutex_init所產生的效果是爲定義的。
所以,POSIX提供了pthread_once函數確保這個語義的實現。
#include <pthread.h>
int pthread_once(pthread_once_t *once_control, void (*init_routine)(void)); //動態初始化
函數成功返回0,不成功返回非零的錯誤碼,沒有定義必須檢測的錯誤類型
pthread_once_t once_control = PTHREAD_ONCE_INIT; //靜態初始化
例1:最多執行一次
用pthread_once來初始化一個變量,並且打印出一條語句的函數
#include <pthread.h>
#include <stdio.h>
static pthread_once_t initonce = PTHREAD_ONCE_INIT;
int var;
static void initialization(void)
{
var = 1;
printf(“The variable was initialized to %d/n”, var);
}
int printfinitonce(void)
{
return pthread_once(&initonce, initialization);
}
函數initialization最多執行一次
例2:最多執行一次
#include <pthread.h>
#include <stdio.h〉
int printinitmutex(int *var, int value)
{
int error;
static int done = 0;
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
if ( (error = pthread_mutex_lock(&lock)) != 0 )
{
return error;
}
if ( done == 0 )
{
*var = value;
printf (“The variable was initialized to %d/n”, value);
done = 1;
}
return pthread_mutex_unlock(&lock);
}
討論1:如果去除done和lock的static限定符會發生如下結果
結果:在一個塊的內部作用於變量的static限定符確保了他們在塊的後繼執行過程中始終存在。若果沒有static修飾,done和lock就變成了自動變量。在這種情況下,每個隊printinitmutex的調用都會分配新的變量,而每次return都會釋放掉這些變量,函數將不再工作。
討論2:將done和lock的申明放到printinitmutex函數外,結果如下
結果:函數printinitmutex可以正常工作。但是,定義在同一個文件中的其他函數就可以訪問done和lock了。將他們保持在函數內部可以更安全的保證”最多一次”
1.4條件變量
條件變量是線程可以使用的另一種同步機。條件變量給多個線程一個會合的場所。條件變量與互斥量一起使用時,允許線程以無競爭的方式等待特定的條件發生。
考慮如下問題:
假設兩個變量x和y被多個線程共享。我們希望線程一直等到x和y相等爲止。
典型的不正確的忙等解決方法是:while(x != y);
根據線程的調度方式,執行忙等的線程可能永遠都不會讓其他的線程使用cpu,這樣x和yde 值永遠也不會變了。同時對共享變量的訪問也應該被保護起來。
等待斷言x==y 爲真的正確的非忙等策略:
a.鎖定互斥量
b.測試條件x==y
c.如果爲真,解除對互斥量的鎖定,並推出循環
d.如果爲假,將線程掛起,並解除對互斥量的鎖定
問題在於:如何保證解除互斥量的鎖定和線程掛起之間x和y沒有發生改變。
解決辦法:解除鎖定和掛起必須是原子操作(pthread_cond_wait)。
1.4.1創建和銷燬條件變量
POSIX用pthread_cond_t類型的變量來表示條件變量。
將NULL傳給attr將用默認屬性初始化一個條件變量
創建:
#include <pthread.h>
int pthread_cond_init(pthread_cond_t cond, const pthread_condattr_t *restrict attr);
//動態初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
//靜態初始化
函數pthread_cond_init成功返回0,不成功返回非零的錯誤碼:
EAGAIN 系統缺乏初始化*cond所需要的非內存資源
ENOMEM 系統缺乏初始化*cond所需要的內存資源
銷燬:
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
成功返回0,不成功返回非零的錯誤碼,沒有定義必須檢測的錯誤碼。
試圖初始化一個已經被初始化了的條件變量;引用一個已經被銷燬的條件變量;銷燬一個使其他線程阻塞了的條件變量,這些行爲都是爲定義的。儘量避免
1.4.2等待和通知條件變量
條件變量是和斷言(條件測試)一起調用的。條件變量的名字就是同這個斷言相關聯的。
通常線程會對一個條件進行測試,測試失敗,就調用pthread_cond_wait或pthread_cond_timewait。
函數中cond指向條件變量,mutex指向互斥量,線程在調用之前應該擁有這個互斥量。當線程被放在條件變量等待隊列中是,等待隊列會使線程釋放這個互斥量。
函數pthread_cond_timewait的第三個參數是一個指向返回時間的指針,這個時間是絕對時間而不是時間間隔。
等待:
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
int pthread_cond_timewait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
成功返回0,不成功返回非零的錯誤碼;abstime指定的時間到期,則返回ETIMEOUT。
例:使線程(非忙)等待,直到a大於或等於b爲止(清楚起見省略了錯誤檢測)
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //變量類型必須是靜態
pthread_mutex_lock(&mutex); //調用pthread_cond_wait之前線程必須擁有互斥量
while(a < b) //pthread_cond_wait返回之後必須再次檢測條件是否滿足
{
pthread_cond_wait(&cond, &mutex); //原子的釋放互斥量,並阻塞
}
pthread_mutex_unlock(&mutex);
pthread_cond_wait返回有可能是假喚醒,因爲只要條件測試中涉及的變量改變就會通知條件變量的等待隊列,函數返回只能說明變量有改變,必須重新進行測試,不滿足則繼續阻塞。
pthread_cond_wait只是提供解鎖和阻塞線程的原子操作,並提供線程排隊功能,當條件改變時對互斥量加鎖返回。條件變量可以看成是隊列的名稱(自己定義的)
通知:
當一個線程改變了可能會使斷言成真的變量時,他應該喚醒一個或多個在等待斷言成真的線程。
pthread_cond_signal函數至少解除了一個阻塞在cond指向的條件變量上的線程的阻塞。
pthread_cond_broadcast函數解除所有阻塞在cond指向的條件變量上的線程的阻塞。
但是,這兩個函數只是解除線程在條件變量上的阻塞,並沒有解除互斥量上的阻塞。
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
一些使用條件變量必須遵守的規則:
a.測試斷言之前獲得互斥量。
b. pthread_cond_wait返回之後重新對斷言進行測試。
因爲返回可能是由某些不相關的事件或無法使斷言成真的pthread_cond_signal引起的,
c.在修改斷言中出現的任一互斥量之前,要獲得互斥量。
d.通常測試斷言或者修改共享變量的應用中,使用互斥量這種短時間鎖定的策略
e.pthread_mutex_unlock顯示地釋放互斥量,
pthread_cond_wait隱式地釋放互斥量。
1.5讀寫鎖
一次只有一個線程可以佔有寫模式的讀寫鎖,但是多個線程可以同時佔有讀模式的讀寫鎖,
而互斥量只能同時有一個線程加鎖。
當讀寫鎖是寫加鎖狀態時,在解鎖之前試圖對這個鎖加鎖的線程都會被阻塞。
當讀寫鎖是讀加鎖狀態時,所有以讀模式對他進行加鎖的線程都可以得到訪問權;所有希望以寫模式對此鎖進行加鎖的線程,必須阻塞直到所有的線程釋放讀鎖(這種情況下讀寫鎖通常會阻塞隨後的讀模式鎖請求,避免讀模式鎖長期佔用,而等待的寫模式所請求得不到滿足)。
1.5.1創建和銷燬
與互斥量一樣pthread_rwlock_t 表示讀寫鎖
創建:
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
attr爲NULL時,以默認屬性創建讀寫鎖
成功返回0,不成功返回非零的錯誤碼,函數必須檢測的錯誤碼:
EAGAIN 系統缺乏初始化*rwlock所需的非內存資源
ENOMEM 系統缺乏初始化*rwlock所需的內存資源
EPERM 調用程序沒有適當的權限
銷燬:
#include <pthread.h>
int pthread_rwlock_destroy(pthread_rwlock *rwlock);
成功返回0,不成功就返回一個錯誤碼,沒有定義必須檢測的錯誤。
試圖初始化一個已經初始化了的讀寫鎖,引用一個已經銷燬的讀寫鎖,結果都是未定義的。
1.5.2加鎖和解鎖
pthread_rwlock_rdlock和pthread_rwlock_wrlock函數一直阻塞,直到鎖可用爲止;
pthread_rwlock_tryrdlock和pthread_rwlock_trywrlock函數會立即返回(EBUSY)。
#include <pthread.h>
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);
成功返回0,不成功返回非零的錯誤碼;
如果鎖已經被持有,而無法獲取,pthread_rwlock_tryrdlock和pthread_rwlock_trywrlock返回EBUSY。
情況1:如果線程在一個已經用pthread_rwlock_wrlock獲取的鎖上調用pthread_rwlock_rdlock
結論:POSIX申明,有可能會出現死鎖
情況2:如果線程在一個已經用pthread_rwlock_rdlock獲取的鎖上調用pthread_rwlock_rdlock
結論:線程會持有同一個讀寫鎖上的多個併發的讀鎖。應該確保解除鎖定調用的數目和鎖定調用的數目項匹配,以釋放鎖定。
1.5.3讀寫鎖和互斥鎖的比較
互斥鎖是一種低開銷的同步機制,如果程序中函數只需要在很短的一段時間持有鎖,那麼互斥鎖是相對高效的;
讀寫鎖有一些開銷,所以,當實際的讀操作要花費相當長的時間的時候(如訪問磁盤引起的讀操作),他們的優點就顯現出來了。在這種情況下,嚴格的線性執行效率是很低的。
1.6線程與信號處理
進程中所有線程都共享進程的信號處理函數,但每個線程都有它自己的信號掩碼。
如果有幾個線程都解除了對同一個異步信號的阻塞,系統就從中挑選一個來處理信號。
線程中信號傳遞的類型
異步:傳遞給某些解除了對該信號的阻塞的線程
同步:傳遞給引發(該信號)的線程
定向:傳遞給標識了的線程(pthread_kill)
1.6.1將信號定向到一個特定的線程中
pthread_kill 函數產生信號碼爲sig的信號,並將其傳送到thread指定的線程中去。
#include <pthread.h>
#include <signal.h>
int pthread_kill(pthread_t thread, int sig);
成功返回0,不成功返回非零的錯誤碼並且無信號發送出去,必須檢測的錯誤碼:
EINVAL sig是無效的或不被支持的信號碼
ESRCH 沒有現成對應於指定的ID
例:下面的代碼段會使線程將它自己和整個進程都殺死,儘管pthread_kill將信號傳遞給了一個特定的線程,但處理信號的行爲將影響到整個進程。(默認行爲是終止進程的信號都是這個結果)。
if (pthread_kill(pthread_self(), SIGKILL) != 0)
{
fprintf (stderr, “Failed to commit suicide/n”);
}
1.6.2爲線程屏蔽信號
每個線程都有它自己的信號掩碼。sigprocmask函數在創建其他線程之前,可以被逐線程調用。但當進程中有多個線程是就應該使用pthread_sigmask函數
#include <pthread.h>
#include <signal.h>
int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
成功返回0,不成功返回非零的錯誤碼,
EINVAL 參數how 無效
how的取值:
SIG_SETMASK 會使線程的信號掩碼被set取代,阻塞set中所有的信號,但不阻塞其他
SIG_BLOCK 將set中的信號添加到現成現有的信號掩碼中(阻塞包括set中的信號)
SIG_UNBLOCK 從線程當前的信號掩碼中將set中的信號刪除(不再阻塞set中的信號)
1.6.3線程等待一個或多個信號發生
#include <signal.h>
int sigwait(const sigset_t *restrict set, int *restrict signop);
成功返回0,不成功返回非零的錯誤碼
set參數指定了線程等待的信號集,signop指向的整數將作爲返回值,表明發送信號的數量。
如果信號集中的某個信號在sigwait調用的時候處於未決狀態,那麼sigwait將會無阻塞的返回,返回之前,sigwait將從進程中移出那些處於未決狀態的信號。
如果多個線程在sigwait調用時,等待同一個信號,這是就會出現線程阻塞,當信號遞送時,只有一個線程可以從sigwait中返回。
注意:爲避免錯誤,線程在調用sigwait之前,必須阻塞那些他正在等待的信號。sigwait函數會自動取消信號集的阻塞狀態,直到新的信號被遞送。在返回之前,sigwait會恢復線程的信號屏蔽字。
1.6.3爲信號處理指定專用的線程(多線程的進程中進行信號處理的推薦策略)
爲了防止信號中斷線程,可以把信號加到每個線程的信號屏蔽字中,然後安排專用線程作信號處理。這些專用線程可以進行函數調用,不需要擔心那些函數是安全的,因爲他們的調用來自正常的線程環境,而非傳統的信號處理程序,傳統的信號處理程序通常會中斷線程的正常執行。
具體:主線程在創建線程之前阻塞所有的信號。信號掩碼是從創建線程中繼承的。這樣,所有的線程都將信號阻塞了。然後,專門用來處理信號的線程對那個信號執行sigwait。
例:
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
int quitflag;
sigset_t mask;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t wait = PTHREAD_COND_INITIALIZER;
void *thr_fn(void *arg)
{
int err;
int signo;
for(;;)
{
err = sigwait(&mask, &signo);
if (err != 0)
{
fprintf (stderr, "sigwait failed/n");
exit(1);
}
switch (signo)
{
case SIGINT:
{
printf("/ninterrupt/n");
break;
}
case SIGQUIT:
{
pthread_mutex_lock(&lock);
quitflag = 1;
pthread_mutex_unlock(&lock);
pthread_cond_signal(&wait);
return (0);
}
default:
{
printf("unexpeted signal %d/n", signo);
exit(1);
}
}
}
}
int main(void)
{
int err;
sigset_t oldmask;
pthread_t tid;
sigemptyset (&mask);
sigaddset (&mask, SIGINT);
sigaddset (&mask, SIGQUIT);
if ( (err = pthread_sigmask(SIG_BLOCK, &mask, &oldmask)) != 0 )
{
fprintf (stderr, "SIG_BLOCK ERROR/n");
exit(1);
}
err = pthread_create(&tid, NULL, thr_fn, NULL);
if (err != 0)
{
fprintf (stderr, "pthread_cteate error/n");
exit(2);
}
pthread_mutex_lock(&lock);
while (quitflag == 0)
{
pthread_cond_wait(&wait, &lock);
}
pthread_mutex_unlock(&lock);
quitflag = 0;
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
{
fprintf (stderr, "SIG_SETMASK error/n");
exit(2);
}
exit(0);
}
線程在開始阻塞SIGINT和SIGQUIT。新建線程繼承了現有的信號屏蔽字。
因爲sigwait會解除信號的阻塞狀態,所以只有一個線程用於信號的接收。這使得對主線程進行編碼是不必擔心來自這些信號的中斷。
1.7線程和fork(略)
線程和進程交互
1.8線程和IO
因爲進程中的所喲歐線程共享相同的文件描述符。
考慮兩個線程,在同一時間對同一個文件描述符進行讀寫操作
線程A 線程B
lseek(fd, 300, SEEK_SET); lseek(fd, 700, SEEK_SET);
read(fd, buf1, 100); read(fd, buf2, 100);
如果線程A執行lseek,然後線程B在線程A調用read之前調用lseek,那麼兩個線程最終會讀取同一個記錄。
爲解決這個問題可以使用pread pwrite把偏移量的設定和數據的讀寫成爲一個原子操作。
pread(fd, buf1, 100, 300); pread(fd, buf2, 100, 700);
1.9線程和信號安全的strerror和perror版本
用sigprocmask阻塞信號
用pthread_mutex_lock保持互斥訪問
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int strerror_r (int errnum, char *strerrbuf, size_t buflen)
{
char *buf;
int error1;
int error2;
int error3;
sigset_t maskblock;
sigset_t maskold;
if ( (sigfillset(&maskblock) == -1) ||
(sigprocmask(SIG_SETMASK, &maskblock, &maskold) == -1) )
{
return errno;
}
if ( error1 = pthread_mutex_lock(&lock) )
{
(void)sigprocmask(SIG_SETMASK, &maskold, NULL);
return error1;
}
buf = strerror(errnum);
if ( strlen(buf) >= buflen )
{
error1 = ERANGE;
}
else
{
(void *)strcpy(strerrbuf, buf);
}
error2 = pthread_mutex_unlock(&lock);
error3 = sigprocmask(SIG_SETMASK, &maskold, NULL);
return error1?error1:(error2?error2:error3);
}
int perror_r(const char *s)
{
int error1;
int error2;
sigset_t maskblock;
sigset_t maskold;
if ( (sigfillset(&maskblock) == -1) ||
(sigprocmask(SIG_SETMASK, &maskblock, &maskold) == -1) )
{
return errno;
}
if( error1 = pthread_mutex_lock(&lock) )
{
(void)sigprocmask(SIG_SETMASK, &maskold, NULL);
return error1;
}
perror(s);
error1 = pthread_mutex_unlock(&lock);
error2 = sigprocmask(SIG_SETMASK, &maskold, NULL);
return error1?error1:error2;
}