線程同步基礎知識

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創建並初始化一個互斥量
POSIXpthread_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);
attrNULL則初始化一個帶默認屬性的互斥量。
成功返回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:如果去除donelockstatic限定符會發生如下結果
結果:在一個塊的內部作用於變量的static限定符確保了他們在塊的後繼執行過程中始終存在。若果沒有static修飾,donelock就變成了自動變量。在這種情況下,每個隊printinitmutex的調用都會分配新的變量,而每次return都會釋放掉這些變量,函數將不再工作。
討論2:將donelock的申明放到printinitmutex函數外,結果如下
結果:函數printinitmutex可以正常工作。但是,定義在同一個文件中的其他函數就可以訪問donelock了。將他們保持在函數內部可以更安全的保證最多一次
 
1.4條件變量
條件變量是線程可以使用的另一種同步機。條件變量給多個線程一個會合的場所。條件變量與互斥量一起使用時,允許線程以無競爭的方式等待特定的條件發生。
考慮如下問題:
假設兩個變量xy被多個線程共享。我們希望線程一直等到xy相等爲止。
典型的不正確的忙等解決方法是:while(x != y);
根據線程的調度方式,執行忙等的線程可能永遠都不會讓其他的線程使用cpu,這樣xyde 值永遠也不會變了。同時對共享變量的訪問也應該被保護起來。
等待斷言x==y 爲真的正確的非忙等策略:
a.鎖定互斥量
b.測試條件x==y
c.如果爲真,解除對互斥量的鎖定,並推出循環
d.如果爲假,將線程掛起,並解除對互斥量的鎖定
問題在於:如何保證解除互斥量的鎖定和線程掛起之間xy沒有發生改變。
解決辦法:解除鎖定和掛起必須是原子操作(pthread_cond_wait)。
1.4.1創建和銷燬條件變量
POSIXpthread_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_waitpthread_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);
attrNULL時,以默認屬性創建讀寫鎖
成功返回0,不成功返回非零的錯誤碼,函數必須檢測的錯誤碼:
EAGAIN     系統缺乏初始化*rwlock所需的非內存資源
ENOMEM    系統缺乏初始化*rwlock所需的內存資源
EPERM      調用程序沒有適當的權限
銷燬:
#include <pthread.h>
int pthread_rwlock_destroy(pthread_rwlock *rwlock);
成功返回0,不成功就返回一個錯誤碼,沒有定義必須檢測的錯誤。
試圖初始化一個已經初始化了的讀寫鎖,引用一個已經銷燬的讀寫鎖,結果都是未定義的。
1.5.2加鎖和解鎖
pthread_rwlock_rdlockpthread_rwlock_wrlock函數一直阻塞,直到鎖可用爲止;
pthread_rwlock_tryrdlockpthread_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_tryrdlockpthread_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);
}
線程在開始阻塞SIGINTSIGQUIT。新建線程繼承了現有的信號屏蔽字。
因爲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線程和信號安全的strerrorperror版本
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;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章