1.Pthreads簡介(mac寫的文章調布局不容易,請大家見諒,哈哈)
POSIX線程(POSIX threads),簡稱Pthreads,是線程的POSIX標準。該標準定義了創建和操縱線程的一整套API。在類Unix操作系統(Unix、Linux、Mac OS X等)中,都使用Pthreads作為操作系統的線程。
2.Pthreads數據類型
pthreads的數據類型是結構體類型,里面包含了運行所需要的相關屬性。
pthread_t:線程的標識符
pthread_attr_t:線程屬性,主要包括scope屬性、detach屬性、堆棧地址、堆棧大小、優先級。
pthread_barrier_t:同步屏障數據類型
pthread_mutex_t:mutex數據類型
pthread_cond_t:條件變量數據類型
pthread_key_t:線程私有存儲類型
3.創建Pthreads線程
pthread_create():創建一個線程。
該函數包含4個參數:第一個參數是pthread_t *類型的指針;第二個參數是pthread_attr_t*類型的指針,切有const修飾不可更改;第三個參數是一個指針函數返回值是void *類型,我們只需傳入函數地址即可(使用void *修飾地址);第四個參數是上面指針函數的參數;
返回值:0是成功,非0是失??;
4.Pthreads常用的線程操縱函數
pthread_detach(): 分離線程,使線程處于分離狀態(unjoinable),一旦線程處于分離狀態,該線程終止時底層資源立即被回收(系統自動回收)。
該函數包含一個參數:線程標識符;
返回值:0是成功,非0是失?。?/p>
pthread_join():阻塞當前的線程,直到另外一個線程運行結束。還可以用來顯示的回收終止線程資源,當該函數返回值時線程才算真正意義上的結束(這種方式回收資源被認為是由另一個線程將該資源釋放)。
該函數包含2個參數:第一個參數即被連接線程的線程號;第二個參數比較復雜指,向一個指向被連接線程的返回碼的指針的指針(一般寫null);
返回值:0是成功,非0是失敗;
這里有三點需要注意:
1.被釋放的內存空間僅僅是系統空間,你必須手動清除程序分配的空間,比如 malloc() 分配的空間。
2.一個線程只能被一個線程所連接。
3.被連接的線程必須是非分離的,否則連接會出錯。
pthread_join()有兩種作用:
1.用于等待其他線程結束:當調用 pthread_join() 時,當前線程會處于阻塞狀態,直到被調用的線程結束后,當前線程才會重新開始執行。
2.對線程的資源進行回收:如果一個線程是非分離的(默認情況下創建的線程都是非分離)并且沒有對該線程使用 pthread_join() 的話,該線程結束后并不會釋放其內存空間,這會導致該線程變成了“僵尸線程”。
pthread_exit():終止調用它的線程。
該函數的參數為一個void*的指針,一般填寫線程標識符的id;
注意:exit和pthread_exit以及return的不同
1、在主線程中,在main函數中return了或是調用了exit函數,則主線程退出,且整個進程也會終止,
此時進程中的所有線程也將終止。因此要避免main函數過早結束。
2、在主線程中調用pthread_exit,? 則僅僅是主線程結束,進程不會結束,進程內的其他線程也不會結束,
知道所有線程結束,進程才會終止。
3、在任何一個線程中調用exit函數都會導致進程結束。進程一旦結束,那么進程中的所有線程都將結束
4、return,是函數返回,不一定是線程函數,只有線程函數return,線程才會退出
pthread_kill():第一個參數為pthread_t,第二個參數為int類型的數據。
向指定ID的線程發送sig信號,如果線程的代碼內不做任何信號處理,則會按照信號默認的行為影響整個進程。也就是說,如果你給一個線程發送了SIGQUIT,但線程卻沒有實現signal處理函數,則整個進程退出(慎用)。
pthread_kill(threadid, SIGKILL)也一樣,他會殺死整個進程。
如果要獲得正確的行為,就需要在線程內實現signal(SIGKILL,sig_handler)。
所以,如果int sig的參數不是0,那一定要清楚到底要干什么,而且一定要實現線程的信號處理函數,否則,就會影響整個進程。
那么,如果int sig的參數是0呢,這是一個保留信號,一個作用就是用來判斷線程是不是還活著。
我們來看一下pthread_kill的返回值:
線程仍然活著:0
線程已不存在:ESRCH
信號不合法:EINVAL
pthread_cancel():請求中斷另外一個線程的運行,屬于被動結束線程。這種被動的結束分為兩種,該方法向目標線程發Cancel信號,但如何處理Cancel信號則由目標線程自己決定,或者忽略(當禁止取消時)、或者立即終止(當在取消點 或異步模式下)、或者繼續運行至Cancelation-point(取消點,下面將描述),總之由不同的Cancelation狀態決定,立即終結為異步終結,另外一種為同步終結。
一個線程處理cancel請求的退出操作相當于pthread_exit(PTHREAD_CANCELED)。當然線程可以通過設置為 PTHREAD_CANCEL_DISABLE來拒絕處理cancel請求,稍后會提及。
pthread_setcancelstate(int state, int *oldstate)設置本線程對Cancel信號的反應,state有兩種值:PTHREAD_CANCEL_ENABLE(缺?。┖?PTHREAD_CANCEL_DISABLE分別表示收到信號后設為CANCLED狀態和忽略CANCEL信號繼續運行;old_state如果不為 NULL則存入原來的Cancel狀態以便恢復。(了解)
pthread_setcanceltype(int type, int *oldtype)設置本線程取消動作的執行時機,type有兩種取值:PTHREAD_CANCEL_DEFFERED 和 PTHREAD_CANCEL_ASYCHRONOUS,僅當Cancel狀態為Enable時有效,分別表示收到信號后繼續運行至下一個取消點再退出和 立即執行取消動作(退出);oldtype如果不為NULL則存入運來的取消動作類型值。(了解)
pthread_testcancel(void)檢查本線程是否處于Canceld狀態,如果是,則進行取消動作,否則直接返回。
pthread_testcancel(void)用法如下:
#include <pthread.h>
#include <iostream>
#include <unistd.h>
using std::endl;
using std::cout;
void* test_cannel(void* arg)
{
? pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
? pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);
? while(true)
? {
? ? pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
? ? cout << "1" << endl;
? ? cout << "2" << endl;
? ? cout << "3" << endl;
? ? cout << "4" << endl;
? ? cout << "5" << endl;
? ? cout << "6" << endl;
? ? cout << "7" << endl;
? ? pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
? ? pthread_testcancel();
? }
}
void* test_no_cannel(void* arg)
{
? //pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);
? while(true)
? {
? ? cout << "1" << endl;
? ? cout << "2" << endl;
? ? cout << "3" << endl;
? ? cout << "4" << endl;
? ? cout << "5" << endl;
? ? cout << "6" << endl;
? ? cout << "7" << endl;
? ? //sleep(1);
? }
}
int main()
{
? pthread_t p1;
? pthread_create(&p1,NULL,test_cannel,NULL);
? sleep(1);
? pthread_cancel(p1);
? pthread_join(p1,NULL);
? return 0;
}
pthread_equal():比較線程ID,線程ID的大小沒有意義。引入原因:在線程中,線程ID的類型是pthread_t類型,由于在Linux下線程采用POSIX標準,所以,在不同的系統下,pthread_t的類型是不同的,比如在ubuntn下,是unsigned long類型,而在solaris系統中,是unsigned int類型。而在FreeBSD上才用的是結構題指針。 所以不能直接使用==判讀,而應該使用pthread_equal來判斷。
pthread_self()功能是獲得線程自身的ID。(獲取的是相對于進程的線程控制塊的首地址, 只是用來描述統一進程中的不同線程,與gettid 獲取的是內核中線程ID不同)。
注意:如果線程處于加鎖、等待、阻塞(掛起狀態)等狀態最好先將相關狀態取消,再進行線程的取消或退出,不然容易發生錯誤(可能導致進程中其他線程產生錯誤結果、死鎖,甚至造成程序崩潰)。
解決方法:
首先介紹一下需要用到的函數:
pthread_cleanup_push(void(*routine) (void*),void*arg)第一個參數為函數名,第二個參數為前面函數的參數
pthread_cleanup_pop(int execute)參數為int類型的數字,以非0參數調用時,引起當前被彈出的線程清理程序執行。
這兩個函數官方將的比較不好理解,以下是網上易懂的解釋:
pthread_cleanup_push注冊一個回調函數,如果你的線程在對應的pthread_cleanup_pop之前異常退出(return是正常退出,其他是異常),那么系統就會執行這個回調函數(回調函數要做什么你自己決定)。但是如果在pthread_cleanup_pop之前沒有異常退出,pthread_cleanup_pop就把對應的回調函數取消了。兩個函數必須成對出現。
有三種情況線程清理函數會被調用:
線程還未執行 pthread_cleanup_pop 前,被 pthread_cancel 取消
線程還未執行 pthread_cleanup_pop 前,主動執行 pthread_exit 終止
線程執行 pthread_cleanup_pop,且 pthread_cleanup_pop 的參數不為 0.
注意:pthread有兩種狀態joinable狀態和unjoinable狀態 一個線程默認的狀態是joinable,如果線程是joinable狀態,當線程函數自己返回退出時或pthread_exit或pthread_cancel時都不會釋放線程所占用堆棧和線程描述符(總計8K多)。只有當你調用了pthread_join之后這些資源才會被釋放。若是unjoinable狀態的線程,這些資源在線程函數退出時或pthread_exit時自動會被釋放。unjoinable屬性可以在pthread_create時指定,或在線程創建后在線程中pthread_detach自己,如:pthread_detach(pthread_self())或者父線程調用pthread_detach(thread_id)結束相應子進程。
5.信號量
信號量機制通過信號量的值控制可用資源的數量。線程訪問共享資源前,需要申請獲取一個信號量,如果信號量為0,說明當前無可用的資源,線程無法獲取信號量,則該線程會等待其他資源釋放信號量(信號量加1)。如果信號量不為0,說明當前有可用的資源,此時線程占用一個資源,對應信號量減1。
舉例:停車場有5個停車位,汽車可使用停車位。在這里5個停車位是共享的資源,汽車是線程。開始信號量為5,表明此時有5個停車位可用。一輛汽車進入停車場前,先查詢信號量的值,不為0表明有可用停車位,汽車進入停車場并使用一個停車位,信號量減1,表明占用一個停車位,可用數減少。
初始化信號量:
int sem_init(sem_t *sem, int pshared, unsigned int val);
該函數第一個參數為信號量指針,第二個參數為信號量類型(一般設置為0),第三個為信號量初始值。第二個參數pshared為0時,該進程內所有線程可用,不為0時不同進程間可用。
信號量減1:
int sem_wait(sem_t *sem);
該函數申請一個信號量,若當前無可用信號量則等待,有可用信號量時占用一個信號量,對信號量的值減1。
信號量加1:
int sem_post(sem_t *sem);
該函數釋放一個信號量,信號量的值加1。
銷毀信號量:
int sem_destory(sem_t *sem);
該函數銷毀信號量。
案例:
? ? ? ? 采用信號量機制,解決蘋果橙子問題:一個能放N(這里N設為3)個水果的盤子,爸爸只往盤子里放蘋果,媽媽只放橙子,女兒只吃盤子里的橙子,兒子只吃蘋果。
? ? ? ? 采用三個信號量:
? ? ? ? 1.sem_t empty:信號量empty控制盤子可放水果數,初始為3,因為開始盤子為空可放水果數為3。
? ? ? ? 2.sem_t? apple ;信號量apple控制兒子可吃的蘋果數,初始為0,因為開始盤子里沒蘋果。
? ? ? ? 3.sem_t orange;信號量orange控制女兒可吃的橙子是,初始為0,因為開始盤子里沒橙子。
注:互斥量work_mutex只為printf輸出時能夠保持一致,可忽略。
#include
#pragma comment(lib, "pthreadVC2.lib")? ? //必須加上這句
sem_t empty;? //控制盤子里可放的水果數
sem_t apple;? //控制蘋果數
sem_t orange; //控制橙子數
pthread_mutex_t work_mutex;? ? ? ? ? ? ? ? ? ? //聲明互斥量work_mutex
void *procf(void *arg) //father線程
? ? ? ? ? {
? ? ? ? ? ? while(1){
? ? ? ? ? ? ? ? sem_wait(&empty);? ? //占用一個盤子空間,可放水果數減1
? ? ? ? ? ? ? ? pthread_mutex_lock(&work_mutex);? ? //加鎖
? ? ? ? ? ? ? ? printf("爸爸放入一個蘋果!\n");
? ? ? ? ? ? ? ? sem_post(&apple);? ? //釋放一個apple信號了,可吃蘋果數加1
? ? ? ? ? ? ? ? pthread_mutex_unlock(&work_mutex);? //解鎖
? ? ? ? ? ? ? ? Sleep(3000);
? ? ? ? ? ? }
? ? ? ? ? }
void *procm(void *arg)? //mother線程
? ? ? ? ? {
? ? ? ? ? ? while(1){
? ? ? ? ? ? ? ? sem_wait(&empty);
? ? ? ? ? ? ? ? pthread_mutex_lock(&work_mutex);? ? //加鎖
? ? ? ? ? ? ? ? printf("媽媽放入一個橙子!\n");
? ? ? ? ? ? ? ? sem_post(&orange);
? ? ? ? ? ? ? ? pthread_mutex_unlock(&work_mutex);? //解鎖
? ? ? ? ? ? ? ? Sleep(4000);
? ? ? ? ? ? }
? ? ? ? ? }
void *procs(void *arg)? //son線程
? ? ? ? ? {
? ? ? ? ? ? while(1){
? ? ? ? ? ? ? ? sem_wait(&apple);? ? ? //占用一個蘋果信號量,可吃蘋果數減1
? ? ? ? ? ? ? ? pthread_mutex_lock(&work_mutex);? ? //加鎖
? ? ? ? ? ? ? ? printf("兒子吃了一個蘋果!\n");
? ? ? ? ? ? ? ? sem_post(&empty);? ? ? //吃了一個蘋果,釋放一個盤子空間,可放水果數加1
? ? ? ? ? ? ? ? pthread_mutex_unlock(&work_mutex);? //解鎖
? ? ? ? ? ? ? ? Sleep(1000);
? ? ? ? ? ? }
? ? ? ? ? }
void *procd(void *arg)? //daughter線程
? ? ? ? ? {
? ? ? ? ? ? while(1){
? ? ? ? ? ? ? ? sem_wait(&orange);
? ? ? ? ? ? ? ? pthread_mutex_lock(&work_mutex);? ? //加鎖
? ? ? ? ? ? ? ? printf("女兒吃了一個橙子!\n");
? ? ? ? ? ? ? ? sem_post(&empty);
? ? ? ? ? ? ? ? pthread_mutex_unlock(&work_mutex);? //解鎖
? ? ? ? ? ? ? ? Sleep(2000);
? ? ? ? ? ? }
? ? ? ? ? }
void main()
{
? ? pthread_t father;? //定義線程
? ? pthread_t mother;
? ? pthread_t son;
? ? pthread_t daughter;
? ? sem_init(&empty, 0, 3);? //信號量初始化
? ? sem_init(&apple, 0, 0);
? ? sem_init(&orange, 0, 0);
pthread_mutex_init(&work_mutex, NULL);? //初始化互斥量
? ? pthread_create(&father,NULL,procf,NULL);? //創建線程
? ? pthread_create(&mother,NULL,procm,NULL);
? ? pthread_create(&daughter,NULL,procd,NULL);
? ? pthread_create(&son,NULL,procs,NULL);
? ? Sleep(1000000000);
}
6.互斥量/鎖
即對象互斥鎖的概念,來保證共享數據操作的完整性。每個對象都對應于一個可稱為" 互斥鎖" 的標記,這個標記用來保證在任一時刻,只能有一個線程訪問該對象。
使用互斥鎖(互斥)可以使線程按順序執行。通常,互斥鎖通過確保一次只有一個線程執行代碼的臨界段來同步多個線程。互斥鎖還可以保護單線程代碼。
要更改缺省的互斥鎖屬性,可以對屬性對象進行聲明和初始化。通常,互斥鎖屬性會設置在應用程序開頭的某個位置,以便可以快速查找和輕松修改。
互斥鎖初始化有兩種方式:
1.普通初始化
pthread_mutex_t mutex_t;//定義互斥鎖變量(結構體)
pthread_mutex_init(&mutex_t,NULL);//初始化線程鎖,第一個參數為pthread_mutex_t *類型的指針,第二個參數為互斥鎖的屬性
2.宏初始化
pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
加鎖:pthread_mutex_lock();//參數為pthread_mutex_t *類型的指針
解鎖:pthread_mutex_unlock();//參數為pthread_mutex_t *類型的指針
嘗試加鎖:pthread_mutex_trylock();//參數為pthread_mutex_t *類型的指針,加鎖成功則返回0,非零則是不成功
銷毀線程鎖:pthread_mutex_destroy ();//參數為pthread_mutex_t *類型的指針,銷毀一個互斥鎖即意味著釋放它所占用的資源,且要求鎖當前處于開放狀態。
鎖的創建和銷毀是對應的在適當的時候記得銷毀線程鎖。
實際操作案例如下:
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
typedef struct ct_sum
{
? ? int sum;
? ? pthread_mutex_t lock;
}ct_sum;
void * add1(void *cnt)
{? ?
? ? pthread_mutex_lock(&(((ct_sum*)cnt)->lock));
? ? for(int i=0; i < 50; i++)
? ? {
? ? ? ? (*(ct_sum*)cnt).sum += i;? ?
? ? }
? ? pthread_mutex_unlock(&(((ct_sum*)cnt)->lock));
? ? pthread_exit(NULL);
? ? return 0;
}
void * add2(void *cnt)
{? ? ?
? ? pthread_mutex_lock(&(((ct_sum*)cnt)->lock));
? ? for(int i=50; i<101; i++)
? ? {
? ? ? ? (*(ct_sum*)cnt).sum += i;?
? ? }
? ? pthread_mutex_unlock(&(((ct_sum*)cnt)->lock));
? ? pthread_exit(NULL);
? ? return 0;
}
int main(void)
{
? ? pthread_t ptid1, ptid2;
? ? ct_sum cnt;
? ? pthread_mutex_init(&(cnt.lock), NULL);
? ? cnt.sum=0;
? ? pthread_create(&ptid1, NULL, add1, &cnt);
? ? pthread_create(&ptid2, NULL, add2, &cnt);
? ? pthread_join(ptid1,NULL);
? ? pthread_join(ptid2,NULL);
? ? printf("sum %d\n", cnt.sum);
? ? pthread_mutex_destroy(&(cnt.lock));
? ? return 0;
}
pthread_mutex_lock()和pthread_mutex_trylock()的區別:
pthread_mutex_lock()是阻塞調用,意思就是如果這個鎖此時正在被其它線程占用, 那么pthread_mutex_lock() 調用會進入到這個鎖的排隊隊列中,并會進入阻塞狀態, 直到拿到鎖之后才會返回。
pthread_mutex_trylock()是非阻塞調用,當請求的鎖正在被占用的時候, 不會進入阻塞狀態,而是立刻返回,并返回一個錯誤代碼 EBUSY,意思是說, 有其它線程正在使用這個鎖,如果加鎖成功則返回0;
pthread_mutex_timedlock()的超時調用:
pthread_mutex_timedlock()也是阻塞調用,但它可以設置超時,不會長時間等待。如下例子,超過1秒未加鎖就會返回錯誤信息。
struct timespec
{
? ? time_t tv_sec;? ? ? ? /* Seconds.? */
? ? long int tv_nsec;? ? ? /* Nanoseconds.? */
};
struct timespec abs_timeout;
abs_timeout.tv_sec = time(NULL) + 1;
abs_timeout.tv_nsec = 0;
int err = pthread_mutex_timedlock(&mtx, &abs_timeout);
if(0 != err) {
? ? if(ETIMEDOUT == err) {
? ? ? ? //The mutex could not be locked before the specified timeout expired.
? ? }
}
7.條件鎖
條件變量機制彌補了互斥機制的缺陷,允許一個線程向另一個線程發送信號(這意味著共享資源某種條件滿足時,可以通過某個線程發信號的方式通知等待的線程),允許阻塞等待線程(當線程等待共享資源某個條件時,可讓該線程阻塞,等待其他線程發送信號通知)。
條件變量機制在處理等待共享資源滿足某個條件問題時,具有非常高的效率,且空間消耗相比互斥機制也有優勢。
條件變量機制,所有等待一個條件變量的線程會形成一個隊列,這個隊列顯然是全局的共享隊列。在調用pthread_cond_wait前加鎖互斥量【必須是普通鎖(PTHREAD_MUTEX_TIMED_NP)或者適應鎖(PTHREAD_MUTEX_ADAPTIVE_NP)】。
傳入前鎖mutex是為了保證線程從條件判斷(我的理解是防止提前喚醒)到進入pthread_cond_wait前,條件不被改變。
如果沒有傳入前的鎖。就會有這樣的情況:線程A判斷條件不滿足之后,調用pthread_cond_wait之前,A休眠。線程B更改了條件,使得條件滿足,但此時線程A還沒有調用pthread_cond_wait。等到線程A又啟動調用pthread_cond_wait后雖然條件滿足,但卻收不到pthread_cond_signal的喚醒,就一直阻塞下去。
傳入后解鎖是為了條件能夠被改變
傳入后的解鎖,是因為調用pthread_cond_signal的那部分,需要先加鎖更改條件后才調用pthread_cond_signal。(更改條件與等待條件滿足,都是針對條件這一個資源的競爭,所以調用pthread_cond_wait和調用pthread_cond_signal的兩個線程需要同一把鎖)如果pthread_cond_wait內不對mutex解鎖,那么在調用pthread_cond_wait后,其他線程就不能更改條件,條件就會一直不滿足。
返回前再次鎖mutex是為了保證線程從pthread_cond_wait返回后 到 再次條件判斷前不被改變。
保證 在pthread_cond_signal之后與解鎖mutex之間可能需要的其他語句能夠執行
對于1,這里的理由與傳入pthread_cond_wait前鎖mutex的理由差不多。如果不鎖,那么線程A調用pthread_cond_wait后,條件滿足,線程A被喚醒,從pthread_cond_wait返回。線程B在此時更改了條件,使得條件不滿足。線程A并不知道條件又被更改,還是以為條件滿足,就可能出錯。
對于2,只要在pthread_cond_signal之后與解鎖mutex之間有其他語句需要執行,那么由于mutex在這時已經被這個線程鎖,還沒有解鎖,所以調用pthread_cond_wait的那個線程在pthread_cond_wait返回前的鎖mutex的行為就會阻塞,直到pthread_cond_signal后的語句執行完解鎖,pthread_cond_wait才會返回。
說到這里就順便說一下,由于pthread_cond_wait返回再次鎖的行為,pthread_cond_signal不一定放在 lock()和unlock()中間。
pthread_cond_init() 該函數第一個參數為條件變量指針,第二個參數為條件變量屬性指針(一般設為NULL)。該函數按照條件變量屬性對條件變量進程初始化。
創建鎖有兩種方式:
1.普通初始化
pthread_cond_t cond_t;//定義互斥鎖變量(結構體)
pthread_cond_init(&cond_t,NULL);//初始化線程鎖,第一個參數為pthread_cond_t *類型的指針,第二個參數為互斥鎖的屬性
2.宏初始化
pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_wait()該函數第一個參數為條件變量指針,第二個為互斥量指針。該函數調用前,需本線程加鎖互斥量,加鎖狀態的時間內函數完成線程加入等待隊列操作 ,線程進入等待前函數解鎖互斥量。在滿足條件離開pthread_cond_wait函數之前重新獲得互斥量并加鎖,因此,本線程之后需要再次解鎖互斥量
pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);該函數第一個參數為條件變量指針,第二個為互斥量指針,第三個參數為時間類型的指針切不可更改。
abstime是一個絕對時間,struct timespce的原型為:
1struct timespec {2? time_t tv_sec;? ? /* Seconds */3long tv_nsec;? ? /* Nanoseconds */4};
其中tv_sec是秒,tv_nsec是納秒(即1000,000,000分之一秒).
該函數的作用線程等待一定的時間,如果超時或有信號觸發,線程喚醒。
pthread_cond_signal()該函數的參數為條件變量指針。該函數向隊列第一個等待線程發送信號,解除這個線程的阻塞狀態。
pthread_cond_broadcast()該函數的參數為條件變量指針。該函數想隊列所有等待線程發送信號,解除這些線程的阻塞狀態。
pthread_cond_destroy()該函數銷毀條件變量。
#include<stdio.h>
#include<pthread.h>
#include<Windows.h>
#include<semaphore.h>
#pragmacomment(lib,"pthreadVC2.lib")//必須加上這句
pthread_tt1;//pthread_t變量t1,用于獲取線程1的ID
pthread_tt2;//pthread_t變量t2,用于獲取線程2的ID? ?
pthread_mutex_tmutex;
pthread_cond_tcond;
inti =0;//共享資源
void*child1(void*arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
i++;
if(i %5==0)
{
pthread_cond_signal(&cond);
}
else
{
printf("我是線程? 1? 打印的數都非5的倍數:? %d \n", i);
}
pthread_mutex_unlock(&mutex);
Sleep(1000);
}
}
void*child2(void*arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex);//獲得信號之前,會重新獲得互斥鎖
printf("我是線程? 2? 打印5的倍數:? %d \n", i);
pthread_mutex_unlock(&mutex);//需要在此處釋放互斥鎖
Sleep(1000);
}
}
intmain(void)
{
pthread_cond_init(&cond,NULL);
pthread_mutex_init(&mutex,NULL);
pthread_create(&t1,NULL, child1,NULL);
pthread_create(&t2,NULL, child2,NULL);
Sleep(100000000);
}
8.讀寫鎖
pthread讀寫鎖把對共享資源的訪問者分為讀者和寫者,讀者只對共享資源進行讀訪問,寫者只對共享資源進行寫操作。在互斥機制,讀者和寫者都需要獨立獨占互斥量以獨占共享資源,在讀寫鎖機制下,允許同時有多個讀者讀訪問共享資源,只有寫者才需要獨占資源。相比互斥機制,讀寫機制由于允許多個讀者同時讀訪問共享資源,進一步提高了多線程的并發度。
? 1.讀寫鎖機制:
寫者:寫者使用寫鎖,如果當前沒有讀者,也沒有其他寫者,寫者立即獲得寫鎖;否則寫者將等待,直到沒有讀者和寫者。
讀者:讀者使用讀鎖,如果當前沒有寫者,讀者立即獲得讀鎖;否則讀者等待,直到沒有寫者。
2.讀寫鎖特性:
同一時刻只有一個線程可以獲得寫鎖,同一時刻可以有多個線程獲得讀鎖。
讀寫鎖出于寫鎖狀態時,所有試圖對讀寫鎖加鎖的線程,不管是讀者試圖加讀鎖,還是寫者試圖加寫鎖,都會被阻塞。
讀寫鎖處于讀鎖狀態時,有寫者試圖加寫鎖時,之后的其他線程的讀鎖請求會被阻塞,以避免寫者長時間的不寫鎖。
3.讀寫鎖基本函數:
讀寫鎖初始化: int pthread_rwlock_init(pthread_rwlock_t * rwlock, const pthread_rwlockattr_t *? attr);
該函數第一個參數為讀寫鎖指針,第二個參數為讀寫鎖屬性指針。函數按讀寫鎖屬性對讀寫鎖進行初始化。
加讀鎖:int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
該函數參數為讀寫鎖指針。函數用于對讀寫鎖加讀鎖。
加寫鎖:int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
該函數參數為讀寫鎖指針。函數用于對讀寫鎖加寫鎖。
釋放讀寫鎖:
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
該函數參數為讀寫鎖指針。函數用于釋放讀寫鎖,包括讀鎖與寫鎖。
銷毀讀寫鎖:int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
該函數參數為讀寫鎖指針。函數用于銷毀讀寫鎖。
以下為讀寫鎖屬性(不詳細講了):
int pthread_rwlock_init(pthread_rwlock_t *rwptr, const pthread_rwlockattr_t *attr)
int pthread_rwlock_destroy(pthread_rwlock_t *rwptr);
都返回:成功時為0,出錯時為正的Exxx值
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
都返回:成功時為0,出錯時為正的Exxx值
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr, int *valptr);
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int valptr);
都返回:成功時為0,出錯時為正的Exxx值
4.案例:
示例使用讀寫鎖,對共享資源data進行讀寫同步,線程readerM,readerN為讀者線程,線程writerA,writerB為寫者線程。
#include
#pragma comment(lib, "pthreadVC2.lib")? ? //必須加上這句
pthread_t t1;? ? ? ? ? //pthread_t變量t1,用于獲取線程1的ID
pthread_t t2;? ? ? ? ? //pthread_t變量t2,用于獲取線程2的ID
pthread_rwlock_t rwlock;? ? ? ? ? ? //聲明讀寫鎖
int data=1;? ? ? ? ? ? ? ? ? ? ? ? ? //共享資源
void* readerM(void* arg)
{
while(1)
{
pthread_rwlock_rdlock(&rwlock);? ? //讀者加讀鎖
printf("M 讀者讀出: %d \n",data);? //讀取共享資源
pthread_rwlock_unlock(&rwlock);? ? //讀者釋放讀鎖
Sleep(1200);
}
return NULL;
}
void* readerN(void* arg)
{
while(1)
{
pthread_rwlock_rdlock(&rwlock);
printf(" N讀者讀出: %d \n",data);
pthread_rwlock_unlock(&rwlock);
Sleep(700);
}
return NULL;
}
void* writerA(void* arg)
{
while(1)
{
pthread_rwlock_wrlock(&rwlock);? ? ? //寫者加寫鎖
data++;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //對共享資源寫數據
printf(" A寫者寫入: %d\n",data);
pthread_rwlock_unlock(&rwlock);? ? ? //釋放寫鎖
Sleep(2000);
}
return NULL;
}
void* writerB(void* arg)
{
while(1)
{
pthread_rwlock_wrlock(&rwlock);
data++;
printf(" B寫者寫入: %d\n",data);
pthread_rwlock_unlock(&rwlock);
Sleep(2000);
}
return NULL;
}
void main(int argc,char** argv)
{
pthread_rwlock_init(&rwlock, NULL);? //初始化讀寫鎖
pthread_create(&t1,NULL,readerM,NULL);
pthread_create(&t1,NULL,readerN,NULL);
pthread_create(&t2,NULL,writerA,NULL);
pthread_create(&t2,NULL,writerB,NULL);
pthread_rwlock_destroy(&rwlock);? ? ? //銷毀讀寫鎖
Sleep(10000000);
return;
}
9.線程私有存儲
所有線程共享程序中的變量?,F在有一全局變量,所有線程都可以使用它,改變它的值。而如果每個線程希望能單獨擁有它,那么就需要使用線程存儲了。表面上看起來這是一個全局變量,所有線程都可以使用它,而它的值在每一個線程中又是單獨存儲的。這就是線程存儲的意義。
下面說一下線程存儲的具體用法。
pthread_key_create()用來創建存儲所需要的環境。該函數有兩個參數,第一個參為pthread_key_t *的指針變量,第二個參數是一個清理函數,用來在線程釋放該線程存儲的時候被調用。該函數指針可以設成NULL,這樣系統將調用默認的清理函數。該函數成功返回0,其他任何返回值都表示出現了錯誤。
當線程中需要存儲特殊值的時候,可以調用pthread_setspcific()。該函數有兩個參數,第一個為前面聲明的pthread_key_t類型的變量,第二個為void*變量,放任意類型數據的指針。
pthread_key_delete()銷毀線程特定數據鍵,參數為pthread_key_t類型的變量。
如果需要取出所存儲的值,調用pthread_getspecific()。該函數的參數為前面提到的pthread_key_t變量,該函數返回void *類型的值。下面是前面提到的函數的原型:
#include<stdio.h>
#include
#include
#include
pthread_key_t key;
structtest_struct {
? ? inti;
? ? floatk;
};
void*child1(void*arg)
{
? ? structtest_structstruct_data;
? ? struct_data.i=10;
? ? struct_data.k=3.1415;
? ? pthread_setspecific(key, &struct_data);
? ? printf("child1--address of struct_data is --> 0x%p\n", &(struct_data));
? ? printf("child1--from pthread_getspecific(key) get the pointer and it points to --> 0x%p\n", (struct test_struct *)pthread_getspecific(key));
? ? printf("child1--from pthread_getspecific(key) get the pointer and print it's content:\nstruct_data.i:%d\nstruct_data.k: %f\n",
? ? ? ? ((struct test_struct *)pthread_getspecific(key))->i, ((struct test_struct *)pthread_getspecific(key))->k);
? ? printf("------------------------------------------------------\n");
}
void*child2(void*arg)
{
? ? inttemp =20;
? ? sleep(2);
? ? printf("child2--temp's address is 0x%p\n", &temp);
? ? pthread_setspecific(key, &temp);
? ? printf("child2--from pthread_getspecific(key) get the pointer and it points to --> 0x%p\n", (int *)pthread_getspecific(key));
? ? printf("child2--from pthread_getspecific(key) get the pointer and print it's content --> temp:%d\n", *((int *)pthread_getspecific(key)));
}
int main(void)
{
? ? pthread_ttid1, tid2;
? ? pthread_key_create(&key, NULL);
? ? pthread_create(&tid1,NULL,child1,NULL);
? ? pthread_create(&tid2,NULL,child2,NULL);
? ? pthread_join(tid1,NULL);
? ? pthread_join(tid2,NULL);
? ? pthread_key_delete(key);
? ? return(0);
}
輸出結果:
child1--address of struct_data is --> 0x0x7ffff77eff40
child1--from pthread_getspecific(key) get the pointer and it points to --> 0x0x7ffff77eff40
child1--from pthread_getspecific(key) get the pointer and print it's content:
struct_data.i:10
struct_data.k: 3.141500
--------------------------------------------
child2--temp's address is 0x0x7ffff6feef44
child2--from pthread_getspecific(key) get the pointer and it points to --> 0x0x7ffff6feef44
child2--from pthread_getspecific(key) get the pointer and print it's content --> temp:20
10.其他方法
pthread_once():某些需要僅執行一次的函數。其中第一個參數為pthread_once_t類型,是內部實現的互斥鎖,保證在程序全局僅執行一次,第二個參數為需要執行的函數名字。
#include<iostream>
#include<pthread.h>
using namespace std;
pthread_once_t once = PTHREAD_ONCE_INIT;
void once_run(void)
{
? ? ? ? cout<<"once_run in thread "<<(unsigned int )pthread_self()<<endl;
}
void * child1(void * arg)
{
? ? ? ? pthread_t tid =pthread_self();
? ? ? ? cout<<"thread "<<(unsigned int )tid<<" enter"<<endl;
? ? ? ? pthread_once(&once,once_run);
? ? ? ? cout<<"thread "<<tid<<" return"<<endl;
}
void * child2(void * arg)
{
? ? ? ? pthread_t tid =pthread_self();
? ? ? ? cout<<"thread "<<(unsigned int )tid<<" enter"<<endl;
? ? ? ? pthread_once(&once,once_run);
? ? ? ? cout<<"thread "<<tid<<" return"<<endl;
}
int main(void)
{
? ? ? ? pthread_t tid1,tid2;
? ? ? ? cout<<"hello"<<endl;
? ? ? ? pthread_create(&tid1,NULL,child1,NULL);
? ? ? ? pthread_create(&tid2,NULL,child2,NULL);
? ? ? ? sleep(10);
? ? ? ? cout<<"main thread exit"<<endl;
? ? ? ? return 0;
}