Chapter 11 線程

來源:互聯網
上載者:User

 

1.線程概念

一個線程由表示一個進程裡的一個執行內容所需的資訊組成。這包括一個在進程裡標識線程的線程ID、一組寄存器值、棧、調用優先順序和策略、訊號掩碼、errno變數(1.7節)、和線程指定資料(12.6節)。在一個進程內的所有東西在進程裡的線程間都可以共用,包括可執行程式的代碼、程式的全域和堆記憶體、棧、和檔案描述符。

 

2.線程標識

就像每個進程有一個進程ID一樣,每個線程也有一個線程ID。進程ID在整個系統的唯一的,但線程ID不同,線程ID只在它所屬的進程環境中有效

1).下面一個函數被用來比較兩個線程ID

#include <pthreads.h>int pthread_equal(pthread_t tid1, pthread_t tid2);//相等返回非0,否則返回0.

 

2).線程可以獲得它自己的線程ID,通過調用pthread_self函數。

#include <pthread.h>pthread_t thread_self(void);//返回調用線程的線程ID。

當線程需要識別以線程ID作為標籤的資料結構時,pthread_self可以和pthread_equal一起使用

 

 

3.線程建立

線程可以通過調用pthread_create函數建立。

#include <pthread.h>int pthread_create(pthread *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void), void *restrict arg);//成功返回0,失敗返回錯誤號碼。

當pthread_create成功返回時,由tidp所指向的記憶體單元被設定為新建立線程的線程ID,attr參數用於定製不同線程屬性.

 

 

4.線程終止

單個線程可以用三種方式退出,在不終止整個進程的情況下停止它的控制流程。

  •  線程可以簡單地從啟動常式退出。傳回值是線程的退出碼。
  •  線程可以被同一進程的其它線程取消。
  •  線程可以調用pthread_exit。

1).退出函數pthread_exit

#include <pthread.h>void pthread_exit(void *rval_ptr);

rval_ptr是無類型指標,和傳入啟動常式的單個參數相似

 

2).rval_ptr指標對進程裡的其它線程可用,通過調用pthread_join函數。

#include <pthread.h>int pthread_join(pthread_t thread, void **rval_ptr);//成功返回0,失敗返回錯誤碼。

調用線程將阻塞,直到指定的線程調用pthread_exit、從它的啟動常式返回或被取消。如果線程簡單地從它的啟動常式返回,那麼rval_ptr將包含返回碼。如果線程被取消,那麼由rval_ptr指定的記憶體單元被設為PTHREAD_CANCELED。

 可以通過調用pthread_join,我們自動把一個線程置於分離狀態(馬上討論)以便它的資源可以被恢複。如果線程已經在分離狀態了,那麼調用pthread_join失敗,返回EINVAL。

 

3).線程可以通過調用pthread_cancel函數來請求同一進程內的另一個線程取消,。

#include <pthread.h>int pthread_cancel(pthread_t tid);//成功返回0,失敗返回錯誤號碼。

在預設情況下,pthread_cancel函數使得tid標識的線程的行為表現為如同調用參數為PTHREAD_CANCELED的pthread_exit。但是,線程可以選擇忽略或控制它如何被取消。我們將在12.7節深入討論。注意pthread_cancel不等待線程終止。它只是發出請求

 

4).線程可以建立多個清理處理機。處理機被儲存在一個棧裡,這意味著它們以被註冊的相反的順序執行(類似於進程當中atexit函數)

#include <pthread.h>void pthread_cleanup_push(void (*rtn)(void *), void *arg);void pthread_cleanup_pop(int execute);

 

當線程執行以下動作時調用清理函數調用參數為arg,清理函數rtn的調用順序是由pthread_cleanup_push函數安排:

a.調用pthread_exit;

b.響應一個取消請求;

c.使用非0execute參數調用pthread_cleanup_pop。

如果execute參數被設為0,那麼清理函數不會被調用。在各種情況下,pthread_cleanup_pop都刪除由最後的pthraed_cleanup_push調用建立的清理處理常式。

進程和線程原始常式的比較
進程原始常式 線程原始常式 描述
fork pthread_create 建立一個新的控制流程
exit pthread_exit 從已有的控制流程中退出
waitpid pthread_join 從控制流程得到退出狀態
atexit pthread_cancel_push 註冊在控制流程退出時調用的函數
getpid pthread_self 得到控制流程的ID
abort pthread_cancel 請求控制流程的異常退出

 

 

5.線程同步

以下概念用處很多人已經很熟悉,這裡只列出線程環境下,所用到的函數及其功能

1).互斥量

互斥體變數由pthread_mutex_t資料類型表示。在使用一個mutex變數之前,我們必須首先初始化它,通過把它設定為常量PTHREAD_MUTEX_INITIALIZER(只對於靜態分配的互斥體),也可以通過pthread_mutex_init函數初始化。如果動態地分配互斥體(例如通過調用malloc),那麼我們需要在釋放記憶體前調用pthread_mutex_destroy

#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);//成功返回0,失敗返回錯誤碼。

為了使用預設屬性初始化一個互斥體,我們把attr設定為NULL

 

對互斥量進行加鎖,需要調用pthread_mutex_lock。如果互斥體已經上鎖了,調用線程將阻塞,直到互斥體被解鎖。對呼哧兩解鎖,調用pthread_mutex_unlock。

#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,失敗返回錯誤號碼。

如果一個線程不能容許阻塞,那麼它可以使用pthread_mutex_trylock來嘗試對互斥量進行加鎖。如果調用pthread_mutex_trylock時互斥量出於未加鎖狀態,那麼pthread_mutex_trylock會不阻塞地鎖住互斥量並返回0。否則,pthread_mutex_trylock將會失敗,返回EBUSY而不鎖住這個互斥量。

 

2).避免死結

 死結是個必要條件:

a.互斥(Mutual exclusion):存在這樣一種資源,它在某個時刻只能被分配給一個執行緒(也稱為線程)使用;

b.持有(Hold and wait):當請求的資源已被佔用從而導致執行緒阻塞時,資源佔用者不但無需釋放該資源,而且還可以繼續請求更多資源;

c.不可剝奪(No preemption):執行緒獲得到的互斥資源不可被強行剝奪,換句話說,只有資源佔用者自己才能釋放資源;

d.環形等待(Circular wait):若干執行緒以不同的次序擷取互斥資源,從而形成環形等待的局面,想象在由多個執行緒組成的環形鏈中,每個執行緒都在等待下一個執行緒釋放它持有的資源

避免死結破壞後三個條件即可(互斥不能被破壞)

 

3).讀寫鎖

讀寫鎖可以有三個狀態:讀模式下加鎖狀態,寫入模式下加鎖狀態,不加鎖狀態。一次只有一個線程可以佔有寫入模式的讀寫鎖,但多個線程可以同時佔有讀模式的讀寫鎖。

讀寫鎖非常適合對資料結構讀的次數遠大於寫的情況。讀寫鎖在寫入模式下時,它所保護的資料結構可以被安全地修改,因為當前只有一個線程可以擁有寫入模式的鎖。當讀寫鎖在讀模式被下,只要線程擷取了讀模式的讀寫鎖,該鎖保護的資料結構可以被多個獲得讀模式鎖的線程讀取。簡而言之,當讀寫鎖以讀模式鎖住時,它是以共用模式鎖住。當它以寫入模式鎖住時,它是以互斥模式鎖住的,所以說讀寫鎖也叫做共用-獨佔鎖。

 

和互斥量一樣,讀寫鎖在使用之前必須初始化,在釋放它們底層的記憶體前必須銷毀

#include <pthread.h>int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);//兩者成功返回0,失敗返回錯誤號碼。

讀寫鎖通過調用phthread_rwlock_init初始化,調用pthread_rwlock_destroy來做清理工作,否則不調用pthread_rwlock_destroy或者調用pthread_rwlock_destroy之前釋放了讀寫鎖佔用的記憶體空間,那麼分配這個鎖的資源就丟失了

 

在讀模式鎖定讀寫鎖,需要調用pthread_rwlock_rdlock。為要在寫入模式下鎖定讀寫鎖,需要調用pthread_rwlock_wrlock。不管以何種方式鎖住讀寫鎖,都可以調用pthread_rwlock_unlock來解鎖它

#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);//三者成功返回0,失敗返回錯誤號碼。

在實現讀寫鎖的時候可能對共用模式下可擷取的鎖的數量上加個限制,所以需要檢查pthread_rwlock_rdlock的傳回值

 

SUS同樣定義了有條件的讀寫鎖原語版本

#include <pthread.h>int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);//兩者成功返回0,失敗返回錯誤號碼。

可以擷取鎖時,函數返回0;否則錯誤EBUSY

 

4).條件變數

條件變數是線程可用的另一個同步機制。條件變數給多個線程提供了一個會合的場所。條件變數和互斥量一起使用時,允許線程以無競爭的方式等待特定條件的發生。

條件變數被使用前必須首先被初始化。pthread_cond_t資料類型代表的條件變數可以以兩種方式初始化。可以把常量PTHREAD_COND_INITIALIZER賦給一個靜態分配的條件變數,但是如果條件變數是動態分配的,可以使用pthread_cond_init函數來初始化它。

在釋放它底下的記憶體前,可以使用pthread_cond_destroy函數對條件變數進行去除初始化。

#include <pthread.h>int pthread_cond_init(pthread_cond_t *restrict cond, pthread_condattr_t *restrict attr);int pthread_cond_destroy(pthread_cond_t *cond);//兩者成功返回0,失敗返回錯誤號碼。

除非需要建立一個非預設屬性條件變數,否則pthread_cond_init的attr參數可以被設定為NULL

 

使用pthread_cond_wait來等待條件為真。如果在給定的時間內條件不能滿足,那麼會產生一個代表出錯碼的返回變數

#include <pthread.h>int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict timeout);//成功返回0,失敗返回錯誤號碼。

傳遞給pthread_cond_wait的互斥量保護這個條件。調用者把鎖住的互斥量傳遞給這個函數,函數把調用線程放在等待這個條件的線程列表上,並解鎖這個互斥體,這兩個操作是原子操作。這樣就關閉了條件被檢查和線程進入休眠狀態等待條件改變這兩個操作的時間間隔,這樣線程不會錯過條件的任何改變。當pthread_cond_wait返回時,互斥量會再次被鎖住。pthread_cond_timedwait函數和pthread_cond_wait函數類似,只是多了個timeout--逾時控制

 

 

有兩個函數來通知線程一個條件已經被滿足。pthread_cond_signal函數將喚醒等待在一個條件上的某個線程,而pthread_cond_broadcast函數將喚醒等待在一個條件上的所有線程。

#include <pthread.h>int pthread_cond_signal(pthread_cond_t *cond);int pthread_cond_broadcast(pthread_cond_t *cond);//成功返回0,失敗返回錯誤號碼。

調用pthread_cond_signal或pthread_cond_broadcast時,也稱為向線程或者條件發送訊號。必須注意一定要在改變條件狀態之後才發送訊號給線程。

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.