線程同步與進程間通信

線程之間天然是共享數據的,所以重點討論線程同步;進程之間進程資源是隔離的,所以重點討論進程間通信。當然有些資源是多進程共享的,所以進程間也需要考慮同步。

線程同步

互斥量 pthread_mutex_t
讀寫鎖 pthread_rwlock_t
條件變量 pthread_cond_t
條件變量讓線程等待特定條件的發生,條件本身是由互斥量保護的。
有兩個函數可以用於通知線程條件已經滿足,pthread_cond_signal將喚醒等待該條件的某個線程,pthread_cond_broadcast將喚醒等待該條件的所有線程(pthread_cond_signal在實現的時候可以喚醒不止一個線程)。
使用pthread_cond_wait和pthread_cond_timewait等待條件的發生。man says “These functions atomically release mutex and cause the calling thread to block on the condition variable cond.”。這兩個函數會原子地釋放互斥量和將調用線程阻塞到條件變量上。調用線程被喚醒後成功獲得互斥量之後函數再返回。
看APUE給出的實例:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
struct msg {
struct msg *m_next;
/* ... more stuff here ... */
};
struct msg *workq;
pthread_cond_t qready = PTHREAD_COND_INITIALIZER;
pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;
void *process_msg(void *p)
{
    struct msg *mp;
        for(;;) {
                pthread_mutex_lock(&qlock);
                while(workq == NULL)
                        pthread_cond_wait(&qready, &qlock);
                mp = workq;
                workq = mp->m_next;
                pthread_mutex_unlock(&qlock);
                /* now process the message mp */
                free(mp);
        }
}
void *enqueue_msg(void *p)
{
        for(;;) {
                struct msg *mp = (struct msg *)malloc(sizeof(struct msg));
                pthread_mutex_lock(&qlock);
                mp->m_next = workq;
                workq = mp;
                pthread_cond_broadcast(&qready);
                pthread_mutex_unlock(&qlock);
                sleep(1);
        }
}
int main()
{
        pthread_t t1,t2;
        int i = 0;
        pthread_create(&t1,NULL,enqueue_msg,NULL);
        for(; i<10; ++i)
        {
                pthread_t t2;
                pthread_create(&t2,NULL,process_msg,NULL);
                pthread_detach(t2);
        }
        pthread_join(t1,NULL);
        return 0;
}

現在結合實例看一下wait操作:調用wait之前肯定要先判斷條件是否滿足,判斷條件之前得先獲得互斥鎖,所以wait必須得釋放互斥鎖;如果wait的兩個操作不是原子的,考慮在釋放互斥鎖之後馬上切換到了生產者線程,生產者獲得互斥鎖產生新的條件並pthread_cond_signal了,那這個signal就會丟失掉,所以兩個操作必須是原子的。
process_msg的流程看起來就像是同步的一樣,通過條件變量異步操作轉換爲了同步操作(Lua的協程是不是就是這樣實現的?)。唯一不像的地方是那個while循環,這個沒辦法,考慮pthread_cond_broadcast喚醒多個等待線程的情況,其他線程可能已經改變了條件,所以必須重新判斷。
最後看一下enqueue_msg中pthread_cond_broadcast和釋放互斥鎖的先後順序。總的來說,兩種都可能是正確的。如果pthread_cond_broadcast在先,broadcast以後,等待線程會被調度運行,它們都會被阻塞在互斥鎖上,等待生產者線程釋放互斥鎖後,一個等待線程會獲得互斥鎖滿足條件然後使條件失效再釋放互斥鎖,然後其他等待線程獲得互斥鎖不滿足條件繼續調用wait等待。如果釋放互斥鎖在先,消費者可以在生產者調用broadcast之前被調度,獲取互斥鎖,然後使條件失效,最後釋放互斥鎖;接着,當調用broadcast時,條件不再爲真,等待線程從wait返回之後再次進入wait等待,這種情況也使得從wait返回之後必須重新檢查條件。

參考
APUE 11.6節和習題11.4
深入解析條件變量(condition variables)

進程間通信

主要工具:信號、管道、fifo、消息隊列、信號量、共享存儲、mmap、記錄鎖
(信號量和記錄鎖主要用於進程間同步)(未計入socket和UNIX域套接字)

總結
System V和POSIX都有信號量和消息隊列,優先考慮使用POSIX。
共享內存用System V的,因爲POSIX的實現尚未完善。
儘可能避免使用消息隊列和信號量,而應當考慮管道和記錄鎖。(from APUE)

在相關進程間共享內存可以考慮使用mmap匿名映射;在無關進程間共享內存,除了XSI共享存儲,也可以使用mmap將統一文件映射至它們的地址空間。(APUE 15.9節)

雖然信號量的意圖在於進程間的同步,互斥量和條件變量的意圖在於線程間同步,但信號量也可用於線程間同步,互斥量和條件變量也可通過共享內存區進行進程間同步。但應該根據具體應用考慮到效率和易用性進行具體的選擇。(三個多線程同步問題及其實現中第一個例子即是信號量用於線程間同步)

TELL_WAIT除了最好使用sigaction函數替代signal函數中用信號的實現,也可以用管道實現(APUE 15.2節 管道),那麼這樣想:一個進程需要某個資源但不可用,於是阻塞住,另一個佔有該資源的進程在釋放資源的時候告訴第一個進程可以解除阻塞使用資源了,只要是一個進程可以通知另一個進程解除阻塞的工具都可以用來實現TELL_WAIT(個人推斷,尚未實踐)。那消息隊列、二元信號量也都可以拿來實現TELL_WAIT。

參考
Unix/Linux的System V、BSD、Posix概念
Linux進程同步之POSIX信號量

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