天天看點

Linux 線程間通信和同步

作者:QT教程

很多時候,我們做項目并不會建立那麼多程序,而是建立一個程序,在該程序中建立多個線程進行工作。

一、程序與線程

1、什麼是程序、線程,有什麼差別?

程序是資源(CPU、記憶體等)配置設定的基本機關,線程是 CPU 排程和配置設定的基本機關(程式執行的最小機關)。如果 CPU 是單核,同一時間隻有一個程序在執行,多核 CPU 可以同一時間點有多個程序在執行。

2、多程序、多線程的優缺點

一個程序由程序控制塊、資料段、代碼段組成,程序本身不可以運作程式,而是像一個容器一樣,先建立出一個主線程,配置設定給主線程一定的系統資源,這時候就可以在主線程開始實作各種功能。

當我們需要實作更複雜的功能時,可以在主線程裡建立多個子線程,多個線程在同一個程序裡,利用這個程序所擁有的系統資源合作完成某些功能。

優缺點:

  1. 一個程序死了不影響其他程序,一個線程崩潰很可能影響到它本身所處的整個程序。
  2. 建立多程序的系統花銷大于建立多線程。
  3. 多程序通訊因為需要跨越程序邊界,不适合大量資料的傳送。多線程無需跨越程序邊界,适合大量資料的傳送。

3、什麼時候用程序,什麼時候用線程

  1. 建立和銷毀較頻繁使用線程,因為建立程序花銷大。
  2. 需要大量資料傳送使用線程,因為多線程切換速度快,不需要跨越程序邊界。
  3. 安全穩定選程序;快速頻繁選線程;

二、線程間通信/同步

上一篇文章我們講了程序間通信的六種方式:管道和 FIFO、信号、消息隊列、信号量、共享記憶體、套接字(Socket),今天我們講一下線程間通信/同步的方式。

線程同步的方法:互斥鎖、條件變量、自旋鎖、讀寫鎖,除此之外,還有信号量、屏障等等,在 Linux 應用開發當中,用的最多的還是互斥鎖和條件變量。

為什麼需要線程同步?

線程同步是在多線程環境下可能需要注意的一個問題。線程的主要優勢在于,資源的共享性,譬如通過全局變量來實作資訊共享,不過這種便捷的共享是有代價的,那就是多個線程并發通路共享資料所導緻的資料不一緻的問題。

1、互斥鎖

互斥鎖(mutex),在通路共享資源之前對互斥鎖進行上鎖,在通路完成後釋放互斥鎖(解鎖);對互斥鎖進行上鎖之後,任何其它試圖再次對互斥鎖進行加鎖的線程都會被阻塞,直到目前線程釋放互斥鎖。如果釋放互斥鎖時有一個以上的線程阻塞,那麼這些阻塞的線程會被喚醒,它們都會嘗試對互斥鎖進行加鎖,當有一個線程成功對互斥鎖上鎖之後,其它線程就不能再次上鎖了,隻能再次陷入阻塞,等待下一次解鎖。

本文福利, 免費領取C++學習資料包、技術視訊/代碼,1000道大廠面試題,内容包括(C++基礎,網絡程式設計,資料庫,中間件,後端開發,音視訊開發,Qt開發等方向的學習路線)↓↓↓↓有需要的可以進企鵝裙927239107領取哦~↓↓

初始化互斥鎖

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);           

加鎖、解鎖

#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);           

當互斥鎖已經被其它線程鎖住時,調用 pthread_mutex_lock()函數會被阻塞,直到互斥鎖解鎖;如果線程不希望被阻塞,可以使用 pthread_mutex_trylock()函數;調用 pthread_mutex_trylock()函數嘗試對互斥鎖進行加鎖,如果互斥鎖處于未鎖住狀态,那麼調用 pthread_mutex_trylock()将會鎖住互斥鎖并立馬傳回,如果互斥鎖已經被其它線程鎖住,調用 pthread_mutex_trylock()加鎖失敗,但不會阻塞,而是傳回錯誤碼 EBUSY。

#include <pthread.h>

int pthread_mutex_trylock(pthread_mutex_t *mutex);           

銷毀互斥鎖(不再需要互斥鎖時,應該将其銷毀)

#include <pthread.h>

int pthread_mutex_destroy(pthread_mutex_t *mutex);           

加鎖、解鎖

#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);           

當互斥鎖已經被其它線程鎖住時,調用 pthread_mutex_lock()函數會被阻塞,直到互斥鎖解鎖;如果線程不希望被阻塞,可以使用 pthread_mutex_trylock()函數;調用 pthread_mutex_trylock()函數嘗試對互斥鎖進行加鎖,如果互斥鎖處于未鎖住狀态,那麼調用 pthread_mutex_trylock()将會鎖住互斥鎖并立馬傳回,如果互斥鎖已經被其它線程鎖住,調用 pthread_mutex_trylock()加鎖失敗,但不會阻塞,而是傳回錯誤碼 EBUSY。

#include <pthread.h>

int pthread_mutex_trylock(pthread_mutex_t *mutex);           

銷毀互斥鎖(不再需要互斥鎖時,應該将其銷毀)

#include <pthread.h>

int pthread_mutex_destroy(pthread_mutex_t *mutex);           

通知條件變量

#include <pthread.h>

int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);           

pthread_cond_signal()函數至少能喚醒一個線程,而 pthread_cond_broadcast()函數則能喚醒所有線程。

等待條件變量

#include <pthread.h>

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

3、自旋鎖

自旋鎖與互斥鎖很相似,在通路共享資源之前對自旋鎖進行上鎖,在通路完成後釋放自旋鎖(解鎖);事實上,從實作方式上來說,互斥鎖是基于自旋鎖來實作的,是以自旋鎖相較于互斥鎖更加底層。

自旋鎖與互斥鎖之間的差別:

  1. 實作方式上的差別:互斥鎖是基于自旋鎖而實作的,是以自旋鎖相較于互斥鎖更加底層;
  2. 開銷上的差別:擷取不到互斥鎖會陷入阻塞狀态(休眠),直到擷取到鎖時被喚醒;而擷取不到自旋鎖會在原地“自旋”,直到擷取到鎖;休眠與喚醒開銷是很大的,是以互斥鎖的開銷要遠高于自旋鎖、自旋鎖的效率遠高于互斥鎖;但如果長時間的“自旋”等待,會使得 CPU 使用效率降低,故自旋鎖不适用于等待時間比較長的情況。
  3. 使用場景的差別:自旋鎖在使用者态應用程式中使用的比較少,通常在核心代碼中使用比較多;因為自旋鎖可以在中斷服務函數中使用,而互斥鎖則不行,在執行中斷服務函數時要求不能休眠、不能被搶占(核心中使用自旋鎖會自動禁止搶占),一旦休眠意味着執行中斷服務函數時主動交出了CPU 使用權,休眠結束時無法傳回到中斷服務函數中,這樣就會導緻死鎖!

初始化和銷毀自旋鎖

#include <pthread.h>

int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
int pthread_spin_destroy(pthread_spinlock_t *lock);           

加鎖和解鎖

#include <pthread.h>

int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);           

4、讀寫鎖

互斥鎖或自旋鎖要麼是加鎖狀态、要麼是不加鎖狀态,而且一次隻有一個線程可以對其加鎖。

讀寫鎖有3 種狀态:讀模式下的加鎖狀态(以下簡稱讀加鎖狀态)、寫模式下的加鎖狀态(以下簡稱寫加鎖狀态)和不加鎖狀态(見),一次隻有一個線程可以占有寫模式的讀寫鎖,但是可以有多個線程同時占有讀模式的讀寫鎖。是以可知,讀寫鎖比互斥鎖具有更高的并行性!

Linux 線程間通信和同步

讀寫鎖有如下兩個規則:

  1. 當讀寫鎖處于寫加鎖狀态時,在這個鎖被解鎖之前,所有試圖對這個鎖進行加鎖操作(不管是以讀模式加鎖還是以寫模式加鎖)的線程都會被阻塞。
  2. 當讀寫鎖處于讀加鎖狀态時,所有試圖以讀模式對它進行加鎖的線程都可以加鎖成功;但是任何以寫模式對它進行加鎖的線程都會被阻塞,直到所有持有讀模式鎖的線程釋放它們的鎖為止。

讀寫鎖非常适合于對共享資料讀的次數遠大于寫的次數的情況。

初始化和銷毀讀寫鎖

#include <pthread.h>

int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);           

加鎖和解鎖

#include <pthread.h>

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);           

繼續閱讀