執行緒的概念、控制、以及同步與互斥
在學習了程序過程序之後,我們就該來學習一下執行緒了,什麼是執行緒呢?接下來我們就來學習一下。
1、執行緒的概念
我們都知道,程序就是程式執行起來的實體,包括一大堆的資料結構,而執行緒是什麼呢?我們應該聽過一句話“執行緒是在程序內部執行的”,如何理解這句話呢?接下來帶著問題我們來學習。
(1)執行緒的概念
什麼是執行緒?簡單來說,執行緒就是一個程式裡的執行流,更準確的說:執行緒是“一個程序內部的控制序列”。一個程序至少有一個執行執行緒(即主執行緒)。
(2)執行緒和程序
- 程序是資源競爭的基本單位;
- 執行緒是程式執行的最小單位;
- 執行緒共享資料,但也有自己私有的一部分資料。
>執行緒ID>一組暫存器>棧>errno>訊號遮蔽字>排程優先順序
線上程中,執行緒擁有自己的執行緒ID在核心中由LWP表示;執行緒私有的一組暫存器存有獨立的上下文資料;以及私有棧空間;當然也就有了私有的排程優先順序;
(3)一程序的多執行緒共享
- 同一地址空間; 既然地址空間都是共享的,那麼Text Segment、Data Segment都是共享的,如果定義一個函式,在各執行緒中都可以呼叫,如果定義一個全域性變數,在各程序中都可以訪問到,除此之外,各執行緒還共享程序資源和環境;
- 檔案描述符表;
- 每種訊號的處理方式(忽略,預設動作,或者自定義)
- 當前工作目錄;
- 使用者ID和組ID;
(4)執行緒的優點
- 建立一個新執行緒的代價比一個新程序小的多;
- 與執行緒之間的切換相比,執行緒之間的切換需要作業系統做的工作要少的多;
- 執行緒佔用的資源比程序少很多;
- 能充分利用多處理器的可並行數量;
- 在等待慢速I/O操作結束的同時,程式可執行其他的計算任務;
- 計算密集型應用,為了能在多處理器系統上執行,將計算分解到多個執行緒中實現;
- I/O密集型應用,為了提高效能,將I/O操作重疊。執行緒可以同時等待不同的I/O操作;
(5)執行緒的缺點
- 效能損失;
- 健壯性降低;一個執行緒崩掉,可能導致整個程序退出
- 缺乏訪問控制;
- 程式設計難度提高;
在瞭解了執行緒之後,我們再來談談程序。我們在前邊學習了程序,在前邊,我們知道程序不簡單的是一段程式它是一段跑起來的程式,是一個實體,當我們學習了執行緒之後,程序就不能直接叫程序了。
在Linux下,程序叫做輕量級程序;Linux下的一個程序由一個或多個PCB、以及資源組成;Linux下程序是承擔分配系統資源的實體;而在Linux下,排程的基本單位不再是程序而是執行緒;“執行緒是在程序內部執行的”這句話現在我們就能理解了,因為程序和執行緒共享地址空間,所以執行緒在程序內部執行也就是執行緒在程序的地址空間中。在Linux下,程序控制塊和執行緒控制塊都由PCB表示,而在Windows作業系統下,執行緒控制塊叫做TCB。
2、可重入函式和執行緒安全
可重入和執行緒安全的概念?它們兩個有什麼區別和聯絡?
一個函式被稱為執行緒安全的,當且僅當被多個併發程序反覆呼叫時,它會一直產生正確的結果。反之,如果一個函式不是執行緒安全的,我們就說它是執行緒不安全的。執行緒不安全有四種情況:
- 不保護共享變數的函式;
- 函式狀態隨著呼叫改變的函式;
- 返回指向靜態變數指標的函式;
- 呼叫執行緒不安全函式的函式;
可重入函式即表示可以被多個執行流重複進入。
可重入函式是執行緒安全的一種。
執行緒安全和可重入的區別:
1、可重入函式是執行緒安全函式的一種,其特點在於它們被多個執行緒呼叫時,不會引用任何共享資料。
可重入函式與執行緒安全的區別與聯絡:
2、執行緒安全是在多個執行緒情況下引發的,而可重入函式可以在只有一個執行緒的情況下來說。
3、執行緒安全不一定是可重入的,而可重入函式則一定是執行緒安全的。
4、如果一個函式中有全域性變數,那麼這個函式既不是執行緒安全也不是可重入的。
5.如果將對臨界資源的訪問加上鎖,則這個函式是執行緒安全的,但如果這個重入函式若鎖還未釋放則會產生死鎖,因此是不可重入的。
6、執行緒安全函式能夠使不同的執行緒訪問同一塊地址空間,而可重入函式要求不同的執行流對資料的操作互不影響使結果是相同的。
3、執行緒控制
我們現在所學的執行緒都是在使用者級庫(POSIX執行緒庫)中使用,與執行緒有關的函式構成了一個完整的系列,絕大數函式的名字都是以“pthread_”打頭的,要使用這些資料庫,要通過引入標頭檔案<pthread.h>,連結這些執行緒函式庫時要使用編譯器命令的“-lpthread”選項。使用者級庫使用時需要載入到地址空間共享區。
(1)建立執行緒
#include<pthread.h>
int pthread_creat(pthread_t *thraed , const pthread_attr_*attr ,
void *(*start_routine)(void*) , void *arg);
引數:thread 返回執行緒ID;
attr 設定執行緒屬性,預設為NULL;
start_routine 為回撥函式地址,執行緒啟動後執行;
arg 傳給回撥函式的引數;
返回值:成功返回0,失敗返回錯誤碼;
錯誤檢查:
- 傳統的函式一般是成功返回0,失敗返回-1.並且對全域性變數errno賦值以指示錯誤。
- pthreads函數出錯時不會設定群居變數errno(而大部分其他的POSIX函式會這樣做),而是將錯誤碼通過返回值返回。
- pthreads同樣也提供了執行緒內的erron變數,以支援其他使用errno的程式碼。對於pthreads函式的錯誤,建議通過返回值來確定,因為讀取返回值要比讀取執行緒內的errno變數的開銷更小。
下面用例項來展示一下:
creat.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
void *Pthread_run(void *arg)
{
int i=0;
for(;;)
{
printf("I'm thread 1 \n");
sleep(1);
}
}
int main()
{
pthread_t tid;
int ret=pthread_create(&tid,NULL,Pthread_run,NULL);
if(ret!=0)
{
fprintf(stderr,"pthread_creat :%s \n",strerror(ret));
exit(EXIT_FAILURE);
}
int i=0;
for(;;)
{
printf("I'm main thread\n");
sleep(1);
}
return 0;
}
makefile
creat:creat.c
gcc -o [email protected] $^ -lpthread
.PHONY:clean
clean:
rm -f creat
(2)程序ID和程序ID
在Linux中,目前的執行緒實現是Native Thread Libaray,簡稱NPTL。在這種實現下,執行緒又稱為輕量級程序,每一個使用者態的執行緒,在核心中都對應一個排程實體,因為擁有自己的程序描述符。
沒有執行緒之前,一個程序對應核心裡的一個程序描述符,對應一個程序ID、但是在引入執行緒概念之後,情況發生了變化,一個使用者程序下管理N個使用者態執行緒,每個執行緒作為一個獨立的排程實體在核心中擁有自己的程序描述符,程序和核心的描述符一下子變成了1:N的關係。
Linux核心引入了執行緒組的概念。
多執行緒的程序,又被稱為執行緒組,執行緒組內的每一個執行緒在核心之中都存在一個程序描述符與之對應。程序描述符結構體中的pid,表面上看對應的是程序ID,其實不然,它對應的是執行緒ID;程序描述符中的tgid,含義是Thread Group ID ,該值對應的是使用者層面的程序ID。
使用者態 | 系統呼叫 | 核心程序描述符中對應的結構 |
執行緒ID | pid_t gettid(void) | pid_t pid |
程序ID | pid_t getpid(void) | pid_t tgid |
現在說的程序ID,不同於pthread_t 型別的執行緒ID,和程序ID一樣,執行緒ID是pid_t型別的變數,而且是用來唯一標識執行緒的一個整形變數。
檢視一個執行緒的ID:
(3)執行緒ID及程序地址空間佈局
- pthread_create函式會產生一個執行緒ID,存放在第一個引數指向的地址中。該執行緒ID和前面說的執行緒ID不是一回事。
- 前面講的執行緒ID屬於程序排程的範疇。因為執行緒是輕量級程序,是作業系統排程器的最小單位,所以需要一個數值來唯一標識該執行緒。
- pthread_create函式產生並標記在第一個引數指向的地址中的執行緒ID中,屬於NPTL執行緒庫的範疇。執行緒庫的後序操作,就是根據該執行緒ID來操作執行緒的。
- 執行緒庫NPTL提供了pthread_self函式,可以獲得自身的ID。
pthread_t pthread_self(void);
pthread_t的型別取決於實現,對於Linux目前實現的NPTL實現而言,pthread_t型別的執行緒ID,本質就是一個程序地址空間上的一個地址。
(4)執行緒終止
我們知道,程序退出有三種情況,那麼執行緒呢?執行緒退出和程序退出有一點不同,就是執行緒退出不存在程式碼沒跑完異常退出的情況,也就是說,執行緒退出有兩種情況,一是程式碼跑完結果正確,二是程式碼跑完結果不正確。
執行緒退出的三種方式:(值終止執行緒,而不終止程序)
- 從執行緒函式return(void *),這種方法對主執行緒不適用,從main函式return相當於呼叫exit。
- 執行緒可以呼叫pthread_exit(void *)終止自己,執行緒不能呼叫exit。
- 執行緒是可以被取消的,一個執行緒可以呼叫函式pthread_cancel 終止同一程序中的另一個執行緒。
下面說一說執行緒退出的兩個函式:
void pthread_exit(void *value_ptr); 執行緒終止函式
引數:value_ptr 不要指向一個區域性變數;
無返回值,跟程序一樣,執行緒結束的時候無法返回到它的呼叫者(自身);
注意:pthread_exit或者return返回的指標所指向的記憶體單元必須是全域性的或者是malloc分配的,不能線上程函式的棧上分配,因為當其他執行緒得到這個返回指標時執行緒函式已經退出了。
int pthread_cancel(pthread_t thread);取消一個執行中的執行緒
引數:thread 執行緒ID
返回值: 成功返回0,失敗返回錯誤碼;
執行緒被取消,返回-1
4、執行緒等待與分離
(1)執行緒等待
為什麼需要執行緒等待?
已經退出的執行緒所佔有的空間未被釋放,任然存在於程序的地址空間內,造成記憶體洩漏;建立新的執行緒時是不會複用剛才退出的執行緒的地址空間;如果不等待,主執行緒直接退出,就標誌著程序退出,執行緒根本沒有機會來執行程式碼,就會被釋放。
執行緒等待函式:
int pthread_join(pthread_t thread, void **value_ptr );
引數:thread 執行緒ID
value_ptr指向一個指標,而該指標指向執行緒的返回值;
返回值:成功返回0,失敗返回錯誤碼;
呼叫該函式的執行緒將掛起等待,知道ID為thread的執行緒終止。thread執行緒以不同的方法終止,通過pthread_join得到的程序終止狀態是不同的:
- 如果thread執行緒通過return返回,value_ptr所指向的單元裡存放的是thread執行緒函式的返回值。
- 如果thread執行緒被別的執行緒呼叫pthread_cancel異常終止,value_ptr所指向的單元裡存放的是常熟PTHREAD_CABCELED(定義的巨集,為(void *)-1)。
- 如果對thread執行緒的終止狀態不感興趣,可以傳NULL給value_ptr引數。
(2)分離執行緒
預設情況下,一個新建立的執行緒是可結合的(joinable),意思就是線上程結束後,需要呼叫pthread_join函式進行等待回收,否則無法回收釋放資源,從而造成記憶體洩漏。
如果不關心執行緒的返回值,join是一種負擔,這個時候,我們可以告訴系統,當執行緒退出後,自動釋放執行緒資源。
int pthread_detachn(pthread_t thread);
分離執行緒可以是自己提出的,也可以由主執行緒對該執行緒進行分離;
pthread_detach(pthread_self);
一個執行緒被分離,就不需要被pthread_join,joinable和分離是衝突的,一個執行緒不能既是joinable又是分離的。
舉栗子說明自己分離:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
void *thread_run(void *arg)
{
pthread_detach(pthread_self());
printf("%s \n",(char *)arg);
return NULL;
}
int main()
{
pthread_t tid;
int id=pthread_create(&tid,NULL,thread_run,"thread 1 is running\n");
if(id!=0)
{
printf("creat thread failure\n");
return 1;
}
int ret=0;
sleep(1);
if(pthread_join(tid,NULL)==0)
{
printf("waiting succeed\n");
ret=0;
}
else
{
printf("waiting failed\n");
ret=1;
}
return ret;
}
結果是先出現一行“thread 1 is running”,然後一秒後,等待失敗。原因就是在那一秒中,thread 1已經把自己給分離了。
一個執行緒出錯掛掉,則相對應的程序也會掛掉;一個被分離的執行緒異常掛掉,程序依舊也會退出。
5、執行緒同步與互斥
在學習程序間通訊時,我們知道要想讓程序間進行通訊,就必須讓兩個程序同時看到同一份公共資源,也就是臨界資源,然後才有可能進行程序間通訊,但是,當兩個程序進行通訊時會產生一些小問題,所以我們就說到了同步與互斥,當然兩個執行緒間進行通訊時,不必那麼麻煩,因為它們兩個本來就在同一個程序中,大多數資源都是共享的,但是隻要有共享的地方,肯定就會出現這樣那樣的問題,所以,執行緒間也需要同步與互斥機制。
(1)mutex互斥量
大部分情況下,執行緒使用的資料都是區域性變數,變數的地址空間線上程自己私有棧空間內,這種情況,變數歸屬於單個執行緒,其他執行緒無法獲得這些變數。但有時候,很多變數需要線上程間共享,這樣的變數稱為共享變數,可以通過資料的共享,完成執行緒之間的互動。但是,多個執行緒併發的操作共享變數,會會帶來一些問題。
例如下面操作共享變數而出現問題的售票系統:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
int ticket=100;
void *sellticket(void *arg)
{
char *tid=(char *)arg;
while(1)
{
if(ticket>0)
{
usleep(1000);
printf("%s sells ticket:%d \n",tid,ticket);
ticket--;
}
else
{
break;
}
}
}
int main()
{
pthread_t t1,t2,t3,t4;
pthread_create(&t1,NULL,sellticket,"thread 1");
pthread_create(&t2,NULL,sellticket,"thread 2");
pthread_create(&t3,NULL,sellticket,"thread 3");
pthread_create(&t4,NULL,sellticket,"thread 4");
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_join(t3,NULL);
pthread_join(t4,NULL);
}
你會發現結果有些不對勁,怎麼還會有負數的票,用實際來說就是一張票賣給了不同的兩個人,這個怎麼可能?根本不符合現實,為什麼會這樣呢?
if語句判斷條件為真以後,程式碼可以併發的切換到其他執行緒;在usleep 的過程中,可能會有很多個執行緒會進入該程式碼段;而且ticket--這個操作也不是一個原子操作,ticket--這一句C語言程式碼對應的彙編程式碼有三條,先將ticket變數由記憶體載入到暫存器中,然後更新暫存器裡面的值(即進行-1操作),再將減完後的值寫回到記憶體中。
要解決以上的問題,就要做到:
- 程式碼必須要有互斥行為:當代碼進入臨界區執行時,不允許其他執行緒進入該臨界區;
- 如果多個執行緒同時要求執行臨界區的程式碼,並且臨界區沒有執行緒執行,那麼只能執行一個執行緒進入該臨界區;
- 如果執行緒不在臨界區中執行,那麼該執行緒不能阻止其他執行緒進入臨界區;
要做到這三點,本質上就需要一把鎖,而在Linux中提供的這把鎖叫互斥量。
而鎖要在進入臨界區之前加,在出了臨界區之後就解鎖。
(2)互斥量的介面
初始化互斥量:
靜態分配:定義一個巨集,直接賦值
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
動態分配:使用庫函式進行初始化
int pthread_mutex_init(pthread_mutex_t *restrict mutex , const pthread_mutexattr_t *restrict attr);
引數:mutex 需要初始化的互斥量
attr 預設為NULL
銷燬互斥量:
銷燬互斥量需要注意:靜態分配的互斥量不需要銷燬;不要銷燬一個已經加鎖的互斥量;已經銷燬的互斥量,要確保後面不會有執行緒再嘗試加鎖。
int pthread_mutex_destroy(pthread_mutex_t * mutex );
互斥量加鎖和解鎖:
int pthread_mutex_lock(pthread_mutex_t * mutex );
int pthread_mutex_unlock(pthread_mutex_t * mutex );
返回值:成功返回0,失敗返回錯誤碼;
呼叫pthread_lock時,可能會遇到以下情況:
互斥量處於未鎖狀態,該函式會將互斥量鎖定,同時返回成功;
發起函式呼叫時,其他執行緒已經鎖定互斥量,或者存在其他執行緒同時申請互斥量,但沒競爭到互斥量,那麼pthread_lock呼叫會陷入阻塞,等待互斥量解鎖。
給上面的例子加個互斥量改進一下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<sched.h>
int ticket=100;
pthread_mutex_t mutex;
void *sellticket(void *arg)
{
char *tid=(char *)arg;
while(1)
{
pthread_mutex_lock(&mutex);
if(ticket>0)
{
usleep(1000);
printf("%s sells ticket:%d \n",tid,ticket);
ticket--;
pthread_mutex_unlock(&mutex);
}
else
{
pthread_mutex_unlock(&mutex);
break;
}
}
}
int main()
{
pthread_t t1,t2,t3,t4;
pthread_mutex_init(&mutex,NULL);
pthread_create(&t1,NULL,sellticket,"thread 1");
pthread_create(&t2,NULL,sellticket,"thread 2");
pthread_create(&t3,NULL,sellticket,"thread 3");
pthread_create(&t4,NULL,sellticket,"thread 4");
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_join(t3,NULL);
pthread_join(t4,NULL);
pthread_mutex_destroy(&mutex);
}
(3)條件變數
當一個執行緒互斥地訪問某個變數時,它可能發現在其他執行緒改變狀態之前,它什麼也做不了。例如當一個執行緒執行緒訪問佇列時,發現佇列為空,它只能等待,只到其他執行緒將一個節點新增到佇列中,而這種時候就需要用到條件變數。這個條件變數將相當於給這個執行緒傳送的訊號一樣,當該執行緒接收到訊號時,就知道佇列不為空了,然後就可以訪問了。
條件變數函式:
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); 初始化
引數:cond 要初始化的變數
attr 預設為NULL;
int pthread_cond_destroy(pthread_cond_t *cond); 銷燬
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 );給某個執行緒單獨通知
簡單案例
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
pthread_cond_t cond;
pthread_mutex_t mutex;
void *r1(void *arg)
{
while(1)
{
pthread_cond_wait(&cond,&mutex);
printf("active \n");
}
}
void *r2(void *arg)
{
while(1)
{
pthread_cond_signal(&cond);
sleep(1);
}
}
int main()
{
pthread_t t1,t2;
pthread_cond_init(&cond,NULL);
pthread_mutex_init(&mutex,NULL);
pthread_create(&t1,NULL,r2,"thread 1");
pthread_create(&t2,NULL,r1,"thread 2");
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
}
結果就是每隔一秒列印一個active。
為什麼pthread_cond_wait需要互斥量?
- 等待條件是執行緒間同步的一種手段,如果只有一個執行緒,條件不滿足,一直等下去都不會滿足,所有必須要有一個執行緒通過某些操作,改變共享變數,使原先不滿足的條件變得滿足,並且友好的通知等待在條件變數上的執行緒。
- 條件不會無緣無故的變得滿足,必然會牽扯到共享資料的變化。所有一定要用互斥鎖來保護,沒有互斥鎖就無法安全的獲取和修改資料。
其實簡單來說就是用來釋放鎖,監測條件變數。但必須是原子的。
(4)條件變數使用規範
等待條件程式碼:
pthread_mutex_lock(&mutex);
while(條件為假)
pthread_cond_wait(cond,mutex);
修改條件
pthread_mutex_unlock(&mutex);
給條件傳送訊號程式碼:
pthread_mutex_lock(&mutex);
設定條件為真
pthread_cond_signal(cond);
pthread_mutex_inlock(&mutex);
(5)生產者消費者模型
生產者消費者模型簡單來說就是“321”原則,“321”原則就是三種關係(生產者與生產者之間互斥關係,消費者與消費者之間互斥關係,消費者與生產者之間互斥且同步),兩種角色(生產者與消費者),一個交易場所(可以是各種資料結構,連結串列,陣列)。
最簡單的就是一個生產者一個消費者,然後再加一個交易場所。(兩個執行緒來模擬生產者消費者模型)---基於連結串列
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<time.h>
#include<assert.h>
typedef struct linklistNode
{
int key;
struct linklistNode *Next;
}Node,*pNode,**ppNode;
pNode head;
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;
void Initlinklist(ppNode head)
{
assert(head);
(*head)->Next=NULL;
}
int isEmpty(pNode head)
{
return head->Next==NULL;
}
pNode NewNode(int key)
{
pNode node=(pNode)malloc(sizeof(Node));
if(node!=NULL)
{
node->key=key;
node->Next=NULL;
}
return node;
}
void PushFront(pNode head,int key)
{
assert(head);
if((head->Next)==NULL)
{
head->Next=NewNode(key);
return;
}
pNode newnode=NewNode(key);
newnode->Next=head->Next;
head->Next=newnode;
}
void PopFront(pNode head,int * key)
{
assert(head);
if(head==NULL)
{
return;
}
pNode del=head->Next;
*key=del->key;
head->Next=del->Next;
free(del);
}
void Destroy(pNode head)
{
assert(head);
pNode cur=head;
pNode del=NULL;
while(cur!=NULL)
{
del=cur;
cur=cur->Next;
free(del);
}
head->Next=NULL;
}
void *Productor(void *arg)
{
int key=0;
while(1)
{
pthread_mutex_lock(&lock);
key=rand()%101;
PushFront(head,key);
printf("Productor push %d\n",key);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&lock);
sleep(5);
}
}
void *Customer(void *arg)
{
int key=0;
while(1)
{
pthread_mutex_lock(&lock);
while(head->Next==NULL)
{
printf("waiting for date\n");
pthread_cond_wait(&cond,&lock);
}
PopFront(head,&key);
printf("Customer pop %d\n",key);
pthread_mutex_unlock(&lock);
//sleep(1);
}
}
int main()
{
head=(pNode)malloc(sizeof(Node));
Initlinklist(&head);
pthread_t t1,t2;
srand(time(NULL));
pthread_create(&t1,NULL,Productor,NULL);
pthread_create(&t2,NULL,Customer,NULL);
Destroy(head);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
return 0;
}
我實現的是消費者快,而生產者慢的例項,所以當生產者生成一個,消費者拿走之後,其餘四秒鐘消費者都在等待生產者生產。(6)POSIX訊號量
POSIX訊號量和SystemV訊號量作用相同,都是用於同步操作,達到無衝突的訪問共享資源的目的,單POSIX可以用於執行緒間同步。
初始化訊號量:
#include<semaphore.h>
int sem_init(sem_t *sem, int pshared,unsigned int value);
引數: pshared 0表示執行緒間共享,非0表示程序間共享;預設為0;
value 訊號量初始值;
銷燬訊號量:
int sem_destroy(sem_t *sem);
等待訊號量:
int sem_wait(sem_t *sem); 等待訊號量,若是等待成功訊號量的值減1,相當於P操作;
釋出訊號量:
int sem_post(sem_t *sem); 釋出訊號量,表示資源使用完畢,可以歸還資源了,將訊號量值加1;
上邊的消費者生產者模型是基於連結串列的,其空間可以等他分配,現在寫一個基於固定大小的迴圈佇列重寫消費者生產者模型。(迴圈結構可以用陣列+模運算實現)
#include<stdio.h>
#include<pthread.h>
#include<semaphore.h>
#include<stdlib.h>
#define SIZE 1024
//環形佇列
int arr[SIZE] = {0};
sem_t sem_pro; //描述環形佇列中的空位置
sem_t sem_con; //描述喚醒佇列中的資料
//生產者,只要環形佇列有空位,便不斷生產
void*productor(void*arg){
int data = 0;
int proIndex = 0;
while(1){
//有空位便生產,沒空位便阻塞等消費者消費
sem_wait(&sem_pro);
data = rand()%1234;
arr[proIndex] = data;
printf("product done %d\n",data);
proIndex = (proIndex+1)%SIZE;
//供消費者消費的資料加1
sem_post(&sem_con);
}
}
//消費者,只要環形佇列中有資料,就不斷消費
void*consumer(void*arg){
int data = 0;
int conIndex = 0;
while(1){
//環形佇列中存在資料則消費,不存在資料則阻塞,直到有資料為止
sem_wait(&sem_con);
data = arr[conIndex];
printf("consume done %d\n",data);
conIndex = (conIndex+1)%SIZE;
//最後,消費了一個數據,空位加1
sem_post(&sem_pro);
}
}
int main(){
pthread_t pro,con;
sem_init(&sem_pro,0,SIZE-1); //一開始有很多空位置
sem_init(&sem_con,0,0); //但並沒有資料
pthread_create(&pro,NULL,productor,NULL);
pthread_create(&con,NULL,consumer,NULL);
pthread_join(pro,NULL);
pthread_join(con,NULL);
sem_destroy(&sem_pro);
sem_destroy(&sem_con);
return 0;
}
以上實現的都是單消費者單生產者mo
相關推薦
執行緒的概念、控制、以及同步與互斥
在學習了程序過程序之後,我們就該來學習一下執行緒了,什麼是執行緒呢?接下來我們就來學習一下。1、執行緒的概念 我們都知道,程序就是程式執行起來的實體,包括一大堆的資料結構,而執行緒是什麼呢?我們應該聽過一句話“執行緒是在程序內部執行的”,如何理解這
多執行緒-同步程式碼塊解決執行緒安全問題的解釋以及同步的特點及好處和弊端
package cn.itcast_10; public class SellTicket implements Runnable { // 定義100張票 private int tick
Java多執行緒基礎之物件鎖的同步與非同步
同步:synchronized 同步的概念就是共享,如果不是共享的資源,就沒有必要進行同步。 非同步:asynchronized 非同步的概念就是獨立,相互之間不受到任何制約。 同步的目的就是為了執行緒安全,對於
作業系統(6)程序---程序概念:程序控制、程序狀態、三狀態模型、掛起模型;執行緒概念:使用者執行緒、核心執行緒、輕權執行緒
文章目錄 1:程序相關概念 1. 程序的概念 2. 程序控制塊 3. 程序狀態 4. 三狀態程序模型(就緒、執行、阻塞/等待) 5. 掛起程序模型 2:程序控制
一、多執行緒基礎概念、實現執行緒三種方法、中斷執行緒方法,以及執行緒狀態轉化
1、CPU核心數和執行緒數的關係 1:1的關係,引入超執行緒之後,就是1:2 2、cpu時間輪轉機制,即RR排程 3、程序和執行緒 程序:程式執行資源分配最小單位,程序內部有多個執行緒,多個執行緒之間會共享程序資源 執行緒:CPU排程的最小單位 4、並行和併發
《瘋狂Java講義(第4版)》-----第16章【多執行緒】(控制執行緒、執行緒同步)
控制執行緒 join執行緒 等那個執行緒做完後,當前執行緒再做! import java.lang.Thread; public class MyThread extends Thread{ public MyThread(String name){ super(
[Xcode10 實際操作]八、網路與多執行緒-(20)時間控制元件Timer定時功能
本文將演示時間控制元件Timer定時功能的使用。 在專案導航區,開啟檢視控制器的程式碼檔案【ViewController.swift】 1 import UIKit 2 3 class ViewController: UIViewController { 4 5 ove
Java學習筆記之——執行緒的生命週期、執行緒同步
一. 執行緒的生命週期 新建(new Thrad):建立執行緒後,可以設定各個屬性值,即啟動前 設定 就緒(Runnable):已經啟動,等待CPU調動 執行(Running):正在被CPU排程 阻塞(Blocked):因為一些原因,暫時不能繼續執行 死亡(Dead):執行緒程式
MFC筆記(四)——多執行緒程式設計1:模組、程序、執行緒間的基本概念
一、模組、程序、執行緒 1.1 模組 一段可執行的程式(包括EXE和DLL),其程式程式碼、資料、資源被載入到記憶體中,由系統建立一個數據結構來管理它。這段程式就是一個模組。這裡所說
Hystrix 執行緒 命令名稱commandKey、分組groupKey以及執行緒池劃分threadPoolKey
配置方式:@HystrixCommand(fallbackMethod = "helloFallback", groupKey = "Group1", threadPoolKey = "HystrixFooServiceGaGa") 1.其中commandKey目前還沒有發
執行緒間的通訊、同步方式與程序間通訊方式
1、執行緒間的通訊方式 使用全域性變數 主要由於多個執行緒可能更改全域性變數,因此全域性變數最好宣告為violate使用訊息實現通訊 在Windows程式設計中,每一個執行緒都可以擁有自己的訊息佇列(UI執行緒預設自帶訊息佇列和訊息迴圈,工作執行緒需要手動實現訊息迴圈),因此可以採用訊息進行執行緒間通訊s
Linux中執行緒的同步與互斥、生產者消費模型和讀者寫者問題、死鎖問題
執行緒的同步與互斥 執行緒是一個存在程序中的一個執行控制流,因為執行緒沒有程序的獨立性,在程序內部執行緒的大部分資源資料都是共享的,所以在使用的過程中就需要考慮到執行緒的安全和資料的可靠。不能因為執行緒之間資源的競爭而導致資料發生錯亂,也不能因為有些執行緒因為
三十七、Linux 執行緒——執行緒清理和控制函式、程序和執行緒啟動方式比較、執行緒的狀態轉換
37.1 執行緒清理和控制函式 1 #include <pthread.h> 2 3 void pthread_cleanup_push(void (* rtn)(void *), void *arg); 4 void pthread_cleanup_pop(int execute);
Java多執行緒(三)、執行緒同步
在之前,已經學習到了執行緒的建立和狀態控制,但是每個執行緒之間幾乎都沒有什麼太大的聯絡。可是有的時候,可能存在多個執行緒多同一個資料進行操作,這樣,可能就會引用各種奇怪的問題。現在就來學習多執行緒對資料訪問的控制吧。 由於同一程序的多個執行緒共享同一片儲存空間,在帶來方便
Java多執行緒(二)、執行緒的生命週期和狀態控制
、執行緒的生命週期 執行緒狀態轉換圖: 1、新建狀態 用new關鍵字和Thread類或其子類建立一個執行緒物件後,該執行緒物件就處於新生狀態。處於新生狀態的執行緒有自己的記憶體空間,通過呼叫start方法進入就緒狀態(runnable)。 注意:不能對已經啟動的
測試程式碼,解決java gui swing多執行緒介面假死、僵死問題,實現介面動態重新整理,動態同步更新資料
(原創) 測試程式碼,解決java gui swing多執行緒介面假死、僵死問題,實現介面動態重新整理,動態更新,同步顯示資料 主類: package testguimulitiplethread; /** * * @author Administrator */ public class Mai
Java多執行緒15:Queue、BlockingQueue以及利用BlockingQueue實現生產者/消費者模型
轉自:http://www.cnblogs.com/xrq730/p/4855857.htmlQueue是什麼佇列,是一種資料結構。除了優先順序佇列和LIFO佇列外,佇列都是以FIFO(先進先出)的方式對各個元素進行排序的。無論使用哪種排序方式,佇列的頭都是呼叫remove(
核心執行緒、輕量級程序、使用者執行緒三種執行緒概念解惑(執行緒≠輕量級程序)
執行緒與程序概念 在現代作業系統中,程序支援多執行緒。 程序是資源管理的最小單元; 執行緒是程式執行的最小單元。 即執行緒作為排程和分配的基本單位,程序作為資源分配的基本單位 一個程序的組成實體可以分為兩大部分:執行緒集和資源集。程序中的執行緒是動態的物件;代表了程序指
程序、執行緒與多執行緒概念詳解
一、概述 在windows中,每一個開啟執行的應用程式或後臺程式,比如執行中的QQ、谷歌瀏覽器、網易雲音樂、資源管理器等都是一個程序。我們感覺這些程式是“同時”執行的,但實際上,一個處理器同一時刻只能執行一個程序,只是CPU在高速輪換執行讓我們有這樣的錯覺,我
關於“核心執行緒”、“使用者執行緒”概念的理解
今天偶然談起了程序的相關概念,發現其中有許多不清晰的地方,現就以上的概念做一些研究,所參考的資料全部來自於網路,所以對於其中不正確的地方,歡迎大家給我指正,讓我能夠對以上概念更加清晰。 好,首先理清以上幾個概念,先來看“核心執行緒”。此處要申明的一點是,從我所看到的資料來看