UNP(卷2:進程間通信)—— 第7、8、9章:互斥鎖、條件變量、讀寫鎖、記錄上鎖

互斥鎖

相互排斥(mutual exclusion),用於保護臨界區。

Posix互斥鎖被聲明爲具有pthread_mutex_t數據類型的變量。

如果互斥鎖的變量是靜態分配的,那麼我們可以把它初始化成常值PTHREAD_MUTEX_INITIALIZER

若是動態分配的,必須在運行之時,通過調用pthread_mutex_init函數來初始化它。

#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
                                                         // 返回:成功爲0,出錯則爲正的Exxx值
若給已經鎖住的鎖上鎖,pthread_mutex_lock會阻塞到解鎖爲止pthread_mutex_trylock是非阻塞版本,則會返回EBUSY錯誤

互斥鎖實際保護的是在臨界區中被操縱的數據。(多進程或多線程之間的共享數據)


條件變量

互斥鎖用於上鎖,條件變量則用於等待。條件變量是類型爲pthread_cond_t 的變量。

#include <pthread.h>

int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

int pthread_cond_broadcast(pthread_cond_t *cond);

int pthread_cond_signal(pthread_cond_t *cond);
                                                   // 返回:若成功則爲0,出錯爲Exxx值。
每個條件變量總有一個互斥鎖與之關聯。我們調用pthread_cond_wait等待某個條件爲真時,還會指定其條件變量的地址和所關聯鎖的地址。

pthread_cond_signal只能喚醒等待在相應條件變量上的一個線程pthread_cond_broadcast喚醒阻塞在相應條件變量的所有線程

默認初始化:PTHREAD_MUTEX_INITIALIZER  、PTHREAD_COND_INITIALIZER

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
                                                   // 返回:若成功則爲0,出錯爲Exxx值。

互斥鎖屬性,條件變量屬性

#include <pthread.h>

int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);

int pthread_condattr_init(pthread_condattr_t *attr);
int pthread_condattr_destroy(pthread_condattr_t *attr);
                                                   // 返回:若成功則爲0,出錯爲Exxx值。


獲取屬性、設置屬性

#include <pthread.h>

int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared);
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);

int pthread_condattr_getpshared(const pthread_condattr_t *restrict attr, int *restrict pshared);
int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared);
                                                  // 返回:若成功則爲0,出錯爲Exxx值。
pshared可以是 PTHREAD_PROCESS_PRIVATE 、 PTHREAD_PROCESS_SHARED 

進程終止時內核總是自動清理的唯一同步鎖類型是fcntl記錄鎖

讀寫鎖

數據類型pthread_rwlock_t,若靜態分配,可以用PTHREAD_RWLOCK_INITIALIZER來初始化。

#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,出錯爲Exxx值。


讀寫鎖屬性:

#include <pthread.h>

int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);

int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
                                                     // 返回:若成功則爲0,出錯爲Exxx值。
設置屬性:

#include <pthread.h>

int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *restrict attr, int *restrict pshared);
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);

// 返回:若成功則爲0,出錯爲Exxx值。
pshared值: PTHREAD_PROCESS_PRIVATE 或 PTHREAD_PROCESS_SHARED (不同進程間共享,不僅僅是單進程中線程之間的共享)

一個線程可以被同一進程內的任何其他線程所取消。

#include <pthread.h>

int pthread_cancel(pthread_t thread);
                                            // Compile and link with -pthread.
                                            // 返回:若成功則爲0,出錯爲Exxx值。
爲處理被取消的可能情況,任何線程可以安裝(壓入)和刪除(彈出)清理處理程序
#include <pthread.h>

void pthread_cleanup_push(void (*routine)(void *), void *arg);
void pthread_cleanup_pop(int execute);
                                            // 返回:若成功則爲0,出錯爲Exxx值。
這些處理程序就是發生以下情況時被調用的函數。

  • 調用線程被取消(pthread_cancel)
  • 調用線程自願終止(pthread_exit)

記錄上鎖

POSIX記錄上鎖定義了一個特殊的字節範圍以指定整個文件。待上鎖或解鎖的部分:字節範圍。

Posix fcntl 記錄上鎖:

#include <unistd.h>
#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* struct flock *arg */ );
                                              // 返回:若成功則取決於cmd,出錯爲-1

struct flock {
    ...
    short l_type;    /* Type of lock: F_RDLCK, F_WRLCK, F_UNLCK */
    short l_whence;  /* How to interpret l_start: SEEK_SET, SEEK_CUR, SEEK_END */
    off_t l_start;   /* Starting offset for lock */
    off_t l_len;     /* Number of bytes to lock */
    pid_t l_pid;     /* PID of process blocking our lock (set by F_GETLK and F_OFD_GETLK) */
    ...
};
cmd參數有三個命令:

F_SETLK:獲取(l_type爲F_RDLCK或F_WRLCK)或釋放(l_type爲F_UNLCK)由arg指向的flock結構所描述的鎖。

F_SETLKW:與F_SETLK類似,若無法請求到鎖,調用線程將阻塞到該鎖能夠授予爲止。(W意思是wait)

F_GETLK:檢查由arg指向的鎖以確定是否有某個已存在的鎖會妨礙新鎖授予調用進程。如果當前沒有這樣的鎖存在,由arg指向的flock結構的l_type被置爲F_UNLCK。否則,關於這個已存在鎖的信息將在由arg指向的flock結構中返回(該結構的內容由fcntl函數覆寫),其中包含持有該鎖的進程ID。

應該清楚發出F_GETLK命令後緊接着發出F_SETLK命令不是一個原子操作。也就是說,如果我們發出F_GETLK命令,並且執行該命令的fcntl函數返回時設置l_type的值爲F_UNLCK,那麼跟着立即發出F_SETLK命令不能保證其fcntl函數會成功返回。因爲這兩次命令調用期間可能有另外一個進程運行並獲取了我們想要的鎖。    

其實提供F_GETLK命令的原因在於:當執行F_SETLK命令的fcntl函數返回一個錯誤時,導致該錯誤的某個鎖的信息可由F_GETLK命令返回,從而允許我們確定是哪個進程鎖住了我們所要請求的文件區。但是即使是這樣的情形,F_GETLK命令也可能返回該文件區已解鎖的信息,因爲在F_SETLK和F_GETLK命令之間,該文件區可能被解鎖。

flock結構描述鎖的類型以及待鎖住的字節範圍。

l_whence成員有三個值:

SEEK_SET:l_start相對於文件的開頭解釋;

SEEK_CUR:l_start相對與文件的當前字節偏移解釋;

SEEK_END:l_start相對於文件的末尾解釋。

l_len成員指定從該偏移開始的連續字節數。長度爲0表示鎖住整個文件,一般鎖整個文件如下使用:指定l_whence成員爲SEEK_SET,l_start爲0,l_len爲0。

Posix記錄上鎖稱爲勸告性上鎖(advisory locking)。一個進程能夠無視一個勸告性鎖而寫一個讀鎖定文件,或者讀一個寫鎖定文件。

有些系統提供了強制性上鎖(mandatory locking)。使用強制性鎖後,內核將檢查每個read和write請求,以驗證其操作不會干擾由某個進程持有的某個鎖。對於通常的阻塞式描述字,與某個強制性鎖衝突的read或write將把調用進程投入睡眠,直到該鎖釋放爲止。對於非阻塞式描述字,與某個強制性鎖衝突的read或write將導致它們返回一個EAGAIN錯誤。

對某個特定文件施行強制性上鎖,應滿足:

  • 組成員執行位必須關閉;
  • SGID位必須打開。
強制性鎖不需要新的系統調用。

雖然強制性上鎖有一定作用,但多個進程在更新同一個文件時,仍然會導致混亂。進程之間還是需要某種上鎖形式的協作。

當一個文件區被鎖住時,待處理的讀出者和寫入者的優先級是不可知的。































發佈了98 篇原創文章 · 獲贊 74 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章