一. 線程的基本操作函數
先講述4個基本線程函數,在調用它們前均要包括pthread.h頭文件
1.創建線程函數
int pthread_create(pthread_t *tid,const pthread_attr_t *attr, void *(*func)(void *),void *arg);
注意:
?? 第一個參數為指向線程標識符的指針
?? 第二個參數用來設置線程屬性
?? 第三個參數是線程運行函數的起始地址
?? 最后一個參數是運行函數的參數
函數thread 不需要參數,所以最后一個參數設為空指針;第二個參數我們也設為空指針,這樣將生成默認屬性的線程。創建線程成功后,新創建的線程則運行參數三和參數四確定的函數,原來的線程則繼續運行下一行代碼。<當創建線程成功時,函數返回0,若不為0則說明創建失敗,常見的錯誤返回代碼為EAGAIN和EINVAL>
2.等待線程的結束函數
int pthread_join(pthread_ttid,void**status);
第一個參數為被等待的線程標識符
第二個參數為一個用戶定義的指針,它可以用來存儲被等待線程的返回值
這個函數是一個線程阻塞的函數,調用它的函數將一直等待到被等待的線程結束為止,當函數返回時,被等待線程的資源被收回。
3.取自己線程ID函數
pthread_t pthread_self(void);
? 線程都有一個ID在給定的進程內標識自己。 線程ID由pthread_creat返回,可以取得自己的線程ID。
4.終止線程函數
一個線程的結束有兩種途徑,一種是函數結束,調用它的線程也就結束,另一種方式是通過函數pthread_exit 來實現。
void pthread_exit(void *status);
注:一個線程不能被多個線程等待,否則第一個接收到信號的線程成功返回,其余調用pthread_join 的線程則返回錯誤代碼ESRCH。
唯一的參數是函數的返回代碼
編譯程序:gcc –o target source –lpthread
二.簡單的多線程編程
舉例
#include<stdio.h>
#include<pthread.h>
void thread(void)
{
? ? int i;
? ? for(i=0;i<3;i++)
? ? printf("This is a pthread.\n");
}
運行程序,得到如下結果:
This is the main process。
This is a pthread。
This is the main process。
This is the main process.
This is a pthread.
This is a pthread。
再次運行,可能得到如下結果:
This is a pthread.
This is the main process.
This is a pthread.
This is the main process.
This is a pthread.
This is the main process.
前后兩次結果不一樣,這是兩個線程爭奪CPU資源的最終結果。
線程應用中的同步問題
許多函數是不可重入的,即同時不能運行一個函數的多個拷貝
在函數中聲明的靜態變量常常會帶來一些問題,函數的返回值也會有問題
進程中共享的變量必須用關鍵字volatile來定義
為了保護變量,必須使用信號量、互斥等方法來保證對變量的正確使用
互斥鎖
互斥鎖用來保證一段時間內只有一個線程在執行一段代碼。
先看下面一段代碼這是一個讀/寫程序,它們公用一個緩沖區,并且假定一個緩沖區只能保存一條信息。即緩沖區只有兩個狀態:有信息或沒有信息。
void reader_function(void);
void writer_function(void);
char buffer;
int buffer_has_item=0;
pthread_mutex_t mutex;//這里聲明了互斥鎖變量mutex,結構pthread_mutex_t為不公開的數據類型,其中包含一個系統分配的屬性對象
struct timespec delay;
void main(void)
{
? ?? pthread_t reader;
? ?? delay.tv_sec=2;/*定義延遲時間*/
? ?? delay.tv_nec=0;
? ?? pthread_mutex_init(&mutex,NULL);
? ?? /*用默認屬性初始化一個互斥鎖對象*/?
? ?? pthread_create(&reader,pthread_attr_default,(void *)&reader_function,NULL);
? ?? writer_function();
? }
void writer_function(void){
? ?? while(1) {
? ? ? ? ? pthread_mutex_lock(&mutex);/*鎖定互斥鎖*/
? ? ? ? ? if(buffer_has_item==O){
? ? ? ? ? ? ? ? buffer=make_new_item();
? ? ? ? ? ? ? ? buffer_has_item=1;
? ? ? ? ?? }
? ? ? ?? pthread_mutex_unlock(&mutex);/*打開互斥鎖*/
? ? ? ?? pthreal_delay_np(&delay);
? ? ? }
?}
void reader_function(void)
{
? ?? while(1){
? ? ? ? ?? pthread_mutex_lock(&mutex);
? ? ? ? ?? If(buffer_has_item==1){
? ? ? ? ? ? ? ? consume_item(buffer);
? ? ? ? ? ? ? ? buffer_has_item=O;
? ? ? ? ?? }
? ? ? ? ?? pthread_mutex_unlock(&mutex);
? ? ? ? ?? pthread_delay_np(&delay);
? ? ?? }
}
鎖的聲明:pthread_mutex_t mutex;
鎖的初始化:pthread_mutex_init(&mutex,NULL);
pthread_mutex_lock 聲明開始用互斥鎖上鎖,此后的代碼直至調用pthread_mutex_unlock為止均被上鎖,即同一時間只能被一個線程調用執行。
當一個線程執行到pthread_mutex_lock處時,如果此時另一個線程使用該鎖,則將阻塞此線程,即程序將等待到另一個線程釋放此互斥鎖。
pthread_mutex_destroy(phtread_mutex_t * lock);
pthread_delay_np函數,讓線程睡眠一段時間,
是為了防止一個線程始終占據此函數。
注意:在使用互斥鎖的過程中很有可能會出現死鎖:即兩個線程試圖同時占用兩個資源,并按不同的次序鎖定相應的互斥鎖。此時我們可以使用函數pthread_mutex_trylock,它是函數pthread_mutex_lock的非阻塞版本,當它發現死鎖不可避免時,它會返回相應的信息,程序員可以針對死鎖做出相應的處理。
讀寫鎖
讀寫鎖是從互斥鎖中發展下來的,互斥鎖會將試圖訪問我們定義的保護區的所有進程都阻塞掉,但讀寫鎖與此不同,它會將訪問中的讀操作和寫操作區分開來對待
在某些讀數據比改數據頻繁的應用中,讀寫鎖將會比互斥鎖表現出很大的優越性
讀寫鎖所遵循的規則:
只要沒有進程持有某個給定的讀寫鎖用于寫,那么任意數目的線程都可持有該讀寫鎖用于讀
僅當沒有線程持有某個給定的讀寫鎖用于讀或寫,才能分配該讀寫鎖用于寫。
讀寫鎖的聲明:pthread_rwlock_t? rwlock;
讀寫鎖初始化:pthread_rwlock_init (pthread_rwlock_t *rwlock,? pthread_rwlockattr_t *attr);
例: pthread_rwlock_init (&rwlock,NULL);
為讀進程獲得鎖:pthread_rwlock_rdlock(pthread_rwlock_t *lock);
為寫進程獲得鎖:pthread_rwlock_wrlock(pthread_rwlock_t *lock);
讀寫進程解鎖:pthread_rwlock_unlock(pthread_rwlock_t *lock);
銷毀讀寫鎖:pthread_rwlock_destroy(pthread_rwlock_t *lock);
上述函數執行成功,返回0,如果不為0,則函數執行失敗
條件變量
條件變量是用來等待而不是用來上鎖的。它是通過一種能夠掛起當前正在執行的進程或放棄當前進程直到在共享數據上的一些條件得以滿足。
條件變量操作過程是:首先通知條件變量,然后等待,同時掛起當前進程直到有另外一個進程通知該條件變量為止。
互斥鎖一個明顯的缺點是它只有兩種狀態:鎖定和非鎖定。條件變量通過允許線程阻塞和等待另一個線程發送信號的方法彌補了互斥鎖的不足,它常和互斥鎖一起使用。
工作機制:條件的檢測是在互斥鎖的保護下進行的。如果一個條件為假,一個線程自動阻塞,并釋放等待狀態改變的互斥鎖。如果另一個線程改變了條件,它發信號給關聯的條件變量,喚醒一個或多個等待它的線程,重新獲得互斥鎖,重新評價條件。如果兩進程共享可讀寫的內存,條件變量可以被用來實現這兩進程間的線程同步。?
條件變量的聲明Pthread_cond_t? condtion;
條件變量的初始化pthread_cond_init(pthread_cond_t * cond,pthread_condattr_t *attr);
等待信號pthread_cond_wait(pthread_cond_t * cond,pthread_mutex_t * mutex);
發送信號pthread_cond_signal(pthread_cond_t *cond);
信號量
信號量(semaphore)是另一種加鎖操作,與普通加鎖不同的是,信號量記錄了一個空閑資源數值,信號量的值表示了當前空閑資源的多少。信號量本質上是一個非負的整數計數器,它用來控制對公共資源的訪問。
當公共資源增加時,調用函數sem_post()增加信號量
當信號量值大于0時,才能使用公共資源,使用后,函數sem_wait()減少信號量
當它變為0時,進程將主動放棄處理器進入等待對列
sem_init(sem_t *sem, int pshared, unsigned int val)
這個函數初始化一個信號量sem 的值為val,參數pshared 是共享屬性控制,表明是否在進程間共享。
sem_wait(sem_t *sem);
調用該函數時,若sem為無狀態,調用線程阻塞,等待信號量sem值增加(post )成為有信號狀態;若sem為有狀態,調用線程順序執行,但信號量的值減一。
sem_post(sem_t *sem);
調用該函數,信號量sem的值增加,可以從無信號狀態變為有信號狀態。
sem_destory(sem_t *sem);?
釋放信號量。
三.實驗
事先編輯好數據文件1.dat和2.dat,假設它們的內容分別為1 2 3 4 5 6 7 8 9 10和 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 , 設計一個程序,在這個程序中一共有3個線程,其中兩個線程負責從文件讀取數據到公共的緩沖區,另外一個線程從緩沖區讀取數據作不同的處理(加和乘運算)。
具體要求:
線程1從1.dat將數據讀文件讀到buf1中;
線程2從2.dat 將數據讀到buf2中;
當buf1,buf2有數據時,線程3將buf1和buf2的結果相加和乘法處理,并將結果顯示出來;
實驗代碼
代碼取自https://blog.csdn.net/qq_42316621/article/details/101291603,侵刪
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
int buf1,buf2;
sem_t sem1;//是否可以向buf1中讀入數據
sem_t sem2;//是否可以向buf2中讀入數據
sem_t sem3;//是否可以從buf1中取出數據
sem_t sem4;//是否可以從buf2中取出數據
/*?從1.dat將數據讀文件讀到buf1中 */
void ReadData1(void)
{
? FILE *fp=fopen("1.dat","r");
? while(!feof(fp)){
? ? sem_wait(&sem1);
? ? fscanf(fp,"%d",&buf1);
? ? sem_post(&sem3);
? }
? fclose(fp);
}
/*?從2.dat 將數據讀到buf2中 */
void ReadData2(void)
{
? FILE *fp=fopen("2.dat","r");
? while(!feof(fp)){
? ? sem_wait(&sem2);
? ? fscanf(fp,"%d",&buf2);
? ? sem_post(&sem4);
? }
? fclose(fp);
}
/* 計算 */
void HandleData(void)
{
? while(1){
? ? sem_wait(&sem3);
? ? sem_wait(&sem4);
? ? printf("Pluse: %d+%d=%d\n",buf1,buf2,buf1+buf2);
? ? printf("Multiply: %d*%d=%d\n",buf1,buf2,buf1*buf2);
? ? sem_post(&sem1);
? ? sem_post(&sem2);
? }
}
void main()
{
? pthread_t t1,t2,t3;
? sem_init(&sem1,0,1);
? sem_init(&sem2,0,1);
? sem_init(&sem3,0,0);
? sem_init(&sem4,0,0);
? pthread_create(&t1,NULL,(void *)ReadData1,NULL);
? pthread_create(&t2,NULL,(void *)ReadData2,NULL);
? pthread_create(&t3,NULL,(void *)HandleData,NULL);
? pthread_join(t1,NULL);
}