一 線程函數(shù)
1.1 創(chuàng)建線程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
thread 是線程變量地址
attr是線程屬性,一般為NULL
start_rount 是函數(shù)指針
arg 是函數(shù)指針指向函數(shù)的參數(shù)
1.2 線程退出
void pthread_exit(void *retval);
retval可以把退出值帶回去,例子見線程回收
1.3 線程回收
int pthread_join(pthread_t thread, void **retval);
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
struct Test
{
int num;
int age;
};
void* func(void* arg)
{
struct Test *temp = (struct Test*)arg;
for(int i = 0 ;i < 5; i++)
{
printf("in_sonthread i = %d\n",i);
}
temp->age = 12;
temp->num = 21;
pthread_exit(temp);
return NULL;
}
int main()
{
pthread_t pid;
struct Test jxk;
struct Test* jxktemp = &jxk;
void* ans;
pthread_create(&pid,NULL,func,&jxk);
for(int i = 0; i < 5 ;i++)
{
printf("in_main,i = %d\n",i);
}
pthread_join(pid,&ans);
struct Test* jxk2 = (struct Test*)ans;
printf("num is %d,age is %d\n",jxk2->num,jxk2->age);
return 0;
}
1.4 線程分離:
某些情況下,程序的主線程有自己的其他業(yè)務(wù),如果讓主線程負(fù)責(zé)子線程的資源回收,調(diào)用pthrad_join()只要子線程不退出,主線程就會一致阻塞,主線程的任務(wù)也不能執(zhí)行了。
線程庫提供了線程分離函數(shù) pthread_detach(),調(diào)用這個函數(shù)后指定的子線程可以和主線程分離,當(dāng)子線程退出是,其占用的內(nèi)核資源就被操作系統(tǒng)的其他進程接管并回收了。線程分離后,在主線程中使用pthread_join() 就會收不到子線程資源了。
int pthread_detach(pthread_t thread);
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
struct Test
{
int num;
int age;
};
void* func(void* arg)
{
struct Test *temp = (struct Test*)arg;
for(int i = 0 ;i < 5; i++)
{
printf("in_sonthread i = %d\n",i);
}
temp->age = 12;
temp->num = 21;
printf("子線程:%ld\n",pthread_self());
pthread_exit(temp);
return NULL;
}
int main()
{
pthread_t pid;
struct Test jxk;
pthread_create(&pid,NULL,func,&jxk);
printf("主線程:%ld\n",pthread_self());
pthread_detach(pid);
pthread_exit(NULL);
return 0;
}
主線程打印了自己的id后便退出了線程,子線程會繼續(xù)運行
1.5 其他線程函數(shù)
1.5.1 線程取消
線程取消就是在某些特定情況下,在一個線程中殺死另一個線程。使用這個函數(shù)殺死一個線程需要分兩步:
- 在線程A中調(diào)用線程取消函數(shù) pthread_cancel,指定殺死線程B,這時候線程B是死不了的
- 在線程B中進行一個系統(tǒng)調(diào)用(從用戶區(qū)切換到內(nèi)核區(qū)),否則線程B可以一直運行。
int pthread_cancel(pthread_t thread);
1.5.2 線程ID比較
在Linux中,線程ID本質(zhì)就是一個無符號長整型,因此可以直接使用比較操作符比較兩個線程的ID。但是線程庫是可以跨平臺使用的,在某些平臺上 pthread_t 可能不是一個單純的整型,這種情況下比較兩個線程的ID必須要使用線程比較函數(shù)。
int pthread_equal(pthread_t t1, pthread_t t2);
二 線程同步
假設(shè)有4個線程ABCD,當(dāng)前一個線程A對內(nèi)存中的共享資源進行訪問的時候,其他線程BCD都不可以對這塊內(nèi)存進行操作,直到線程A對這塊內(nèi)存訪問完畢為止,BCD中的一個才能訪問這塊內(nèi)存,剩下的兩個需要繼續(xù)阻塞等待,以此類推,直到所有的線程都完成對這塊內(nèi)存的操作。
線程內(nèi)對這塊內(nèi)存的訪問方式就稱之為線程同步。所謂線程同步不是說多個線程同時對內(nèi)存進行訪問,而是按照先后順序依次進行的。
例子:兩個線程數(shù)數(shù)字
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
int Number;
void* func1(void* arg)
{
for(int i = 0; i < 50; i++)
{
int cur = Number;
cur++;
usleep(100);
Number = cur;
printf("thread A %ld, number is %d\n",pthread_self(),Number);
}
return NULL;
}
void* func2(void* arg)
{
for(int i = 0; i < 50; i++)
{
int cur = Number;
cur++;
Number = cur;
usleep(50);
printf("thread B %ld, number is %d\n",pthread_self(),Number);
}
return NULL;
}
int main()
{
pthread_t pidA,pidB;
pthread_create(&pidA,NULL,func1,NULL);
pthread_create(&pidB,NULL,func2,NULL);
printf("main thread %ld\n",pthread_self());
sleep(1);
}
2.1 互斥鎖
2.1.1定義
pthread_mutex_t mutex;
2.1.2 初始化
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
//attr 是鎖屬性,一般為NULL
restrict 是一個關(guān)鍵字,用來修飾指針,只有這個關(guān)鍵字修飾的指針可以訪問指向的內(nèi)存地址,其他指針都不行。
// p = mutex, p也不能訪問mutex的地址
2.1.3 銷毀
int pthread_mutex_destroy(pthread_mutex_t *mutex);
2.1.4 加鎖 、 常試鎖、解鎖
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
//不會阻塞,會返回一個錯誤號
int pthread_mutex_unlock(pthread_mutex_t *mutex);
2.1.5 互斥鎖使用
解決上訴數(shù)數(shù)問題:
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
int Number;
pthread_mutex_t numMutex;
void* func1(void* arg)
{
for(int i = 0; i < 50; i++)
{
pthread_mutex_lock(&numMutex);
int cur = Number;
cur++;
Number = cur;
printf("thread A %ld, number is %d\n",pthread_self(),Number);
pthread_mutex_unlock(&numMutex);
usleep(100);
}
return NULL;
}
void* func2(void* arg)
{
for(int i = 0; i < 50; i++)
{
pthread_mutex_lock(&numMutex);
int cur = Number;
cur++;
Number = cur;
printf("thread B %ld, number is %d\n",pthread_self(),Number);
pthread_mutex_unlock(&numMutex);
usleep(50);
}
return NULL;
}
int main()
{
pthread_t pidA,pidB;
if(pthread_mutex_init(&numMutex,NULL) != 0)
{
fprintf(stderr,"init mutex lock failed;\n");
return 0;
}
pthread_create(&pidA,NULL,func1,NULL);
pthread_create(&pidB,NULL,func2,NULL);
printf("main thread %ld\n",pthread_self());
pthread_join(pidA,NULL);
pthread_join(pidB,NULL);
pthread_mutex_destroy(&numMutex);
}
2.2 死鎖
造成死鎖的場景:
多個線程訪問共享資源時,需要加鎖,如果鎖使用不當(dāng),就會造成死鎖這種現(xiàn)象。如果線程死鎖造成的后果是:所有的線程被阻塞,并且進行的阻塞是無法解開的(能解開就不會被阻塞了);
- 加鎖之后忘記解鎖
- 加鎖之后,在解鎖之前,程序由其他出口跳出當(dāng)前函數(shù)邏輯(異常、滿足條件的return等)
- 重復(fù)加鎖,造成死鎖
2.3 如何避免死鎖
- 避免多次鎖定,多檢查
- 對共享資源訪問完畢之后,一定要解鎖,或者在加鎖的時候先使用trylock
- 如果程序中有多把鎖,可以控制對鎖的訪問順序(順序訪問資源、但在有些情況下做不到),另外也可以在對其他互斥鎖做加鎖操作之前,先釋放當(dāng)前線程擁有的互斥鎖。
- 項目程序可以引入一些專門用于死鎖檢測的模塊。
2.4 讀寫鎖
2.4.1 基本信息(特點和記錄信息)
讀寫鎖是互斥鎖的升級版,在做讀操作的時候可以提高程序的執(zhí)行效率,如果所有的線程都是做讀操作,那么讀是并行的,但是使用互斥鎖,讀操作也是串行的。
**讀寫鎖是一把鎖。**類型是 pthrad_rwlock_t,有了類型就可以創(chuàng)建一把互斥鎖了。
pthread_rwlock_t rwlock;
這把鎖記錄了這些信息:
- 鎖的狀態(tài):鎖定/打開
- 鎖定的是什么操作:讀操作/寫操作,使用讀寫鎖鎖定讀操作,需要先解鎖才能去鎖定寫操作,反之亦然
- 哪個線程把這把鎖鎖上了。
讀寫鎖的特點:
- 使用讀寫鎖的讀鎖鎖定了臨界區(qū),線程對臨界區(qū)的訪問是并行的,讀鎖是共享的。
- 使用讀寫鎖的寫鎖鎖定了臨界區(qū),線程對臨界區(qū)的訪問是串行的,寫鎖是獨占的。
- 使用讀寫鎖分別對兩個臨界區(qū)加了讀鎖和寫鎖,兩個線程要同時訪問兩個臨界區(qū),訪問寫鎖臨界區(qū)的線程繼續(xù)運行,訪問讀鎖的臨界區(qū)線程阻塞,因為寫鎖的有點急比讀鎖高。
2.4.2 類型定義 、 初始化函數(shù) 和 銷毀函數(shù)
//類型
pthread_rwlock_t rwlock;
//初始化
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
//rwlock,讀寫鎖地址
//attr 讀寫鎖屬性,一般為NULL
//銷毀
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
2.4.3 讀鎖 和 嘗試讀函數(shù)
//在程序中進行讀操作
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
//調(diào)用這個函數(shù),如果讀寫鎖是打開的,那么加鎖成功;
//如果讀寫鎖已經(jīng)鎖定了讀操作,依然可以加鎖成功,因為讀鎖是共享的;
//如果讀寫鎖已經(jīng)鎖定了寫操作,調(diào)用這個函數(shù)會被阻塞。
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
//調(diào)用這個函數(shù),如果讀寫鎖是打開的,那么加鎖成功;
//如果讀寫鎖已經(jīng)鎖定了讀操作,依然可以加鎖成功,因為讀鎖是共享的;
//如果讀寫鎖已經(jīng)鎖定了寫操作,調(diào)用這個函數(shù)加鎖失敗,但是線程不會被阻塞,可以在程序中對函數(shù)返回值進行判斷,添加加鎖失敗后的處理動作。
2.4.4 寫鎖
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
//調(diào)用者函數(shù),如果讀寫鎖是打開的,那么加鎖成功;
//如果已經(jīng)鎖定了讀操作 或 寫操作,調(diào)用這個函數(shù)線程會阻塞。
2.4.5 解鎖
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
2.4.6 讀寫鎖的使用
例:8個線程同時操作一個全局變量,三個線程不定時寫資源,5個線程不定時讀資源。
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>
int Number;
pthread_rwlock_t rwLock;
void* readNum(void* arg)
{
for(int i = 0; i < 50; i++)
{
pthread_rwlock_rdlock(&rwLock);
printf("Thread read, id = %ld, number = %d\n",pthread_self(),Number);
pthread_rwlock_unlock(&rwLock);
usleep(rand() % 5);
}
return NULL;
}
void* writeNum(void* arg)
{
for(int i = 0; i < 50; i++)
{
pthread_rwlock_wrlock(&rwLock);
int cur = Number;
cur++;
Number = cur;
printf("Thread write, id = %ld, number = %d\n",pthread_self(),Number);
pthread_rwlock_unlock(&rwLock);
usleep(5);
}
return NULL;
}
int main()
{
pthread_t pidA[5],pidB[3];
if(pthread_rwlock_init(&rwLock,NULL) != 0)
{
fprintf(stderr,"init mutex lock failed;\n");
return 0;
}
for(int i = 0; i < 5; i++)
{
pthread_create(&pidA[i],NULL,readNum,NULL);
}
for(int i = 0; i < 3; i++)
{
pthread_create(&pidB[i],NULL,writeNum,NULL);
}
//阻塞,資源回收
for(int i = 0; i < 5; i++)
{
pthread_join(pidA[i],NULL);
}
for(int i = 0; i < 3; i++)
{
pthread_join(pidB[i],NULL);
}
pthread_rwlock_destroy(&rwLock);
printf("main thread %ld\n",pthread_self());
return 0;
}
2.5 條件變量
嚴(yán)格意義上說,條件變量的主要作用不是用來處理線程同步,而是進行線程的阻塞。
2.5.1 數(shù)據(jù)類型、初始化 和 釋放
pthread_cond_t cond;
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);
2.5.2 wait 和 timewait
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 abstime);
// 只會阻塞一定的時間長度,時間過了之后就會繼續(xù)往下執(zhí)行
time_t mytim = time(NULL);
struct timespec tmsp;
tmsp.tv_nsec = 0;
tmsp.tv_sec = tume(NULL) + 100; //線程阻塞100s
2.5.3 signal 和 broadcast
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
調(diào)用以上兩個函數(shù)的任意一個,都可以喚醒被 pthread_cond_wait 或者 pthread_cond_timewait 阻塞的線程;
區(qū)別在于:
- pthread_cond_signal 是喚醒最少一個被阻塞的線程(總個數(shù)不定);
- pthread_cond_broadcast是喚醒所有被阻塞的線程。
2.6 信號量
信號量用在多線程多任務(wù)同步的,一個線程完成了某一動作就通過信號量告訴別的線程,別的線程再進行某些動作。信號量不一定是鎖定某一資源,而是流程上的概念,比如:有AB兩個線程,B線程要等A線程完成某一個任務(wù)后在進行自己下面的步驟,這個任務(wù)并不一定是鎖定某一資源,還可以是進行一些計算或者數(shù)據(jù)處理之類。
信號量和條件變量一樣用于處理生產(chǎn)者和消費者模型,用于阻塞生產(chǎn)者線程或消費者線程的運行,
類型為 sem_t ,對應(yīng)頭文件是 <semaphore.h>
#include<semaphore.h>
sem_t sem;
2.6.1 類型定義、初始化 和 銷毀
//類型定義
sem_t sem;
//初始化
int sem_init(sem_t *sem, int pshared, unsigned int value);
// sem:信號量變量地址
// pshared:
// 0:線程同步
// 非0:進程同步
//value: 初始化當(dāng)前信號量擁有的資源(>=0,如果資源數(shù)為0,線程就會被阻塞)
//銷毀
int sem_destroy(sem_t *sem);
2.6.2 sem_wai 和 sem_trywait
//調(diào)用函數(shù)嗲用sem中的資源樹就會消耗一個,資源數(shù) -1
int sem_wait(sem_t* sem);
當(dāng)調(diào)用這個函數(shù),并且sem中的資源數(shù)>0,線程不會阻塞,線程會占用sem中的一個資源,因此資源數(shù)-1,直到sem中的資源減為0時,資源被耗盡,因此線程也就阻塞了。
//調(diào)用函數(shù)嗲用sem中的資源樹就會消耗一個,資源數(shù) -1
int sem_trywait(sem_t* sem);
當(dāng)調(diào)用這個函數(shù),并且sem中的資源數(shù)>0,線程不會阻塞,線程會占用sem中的一個資源,因此資源數(shù)-1,直到sem中的資源減為0時,資源被耗盡,線程不會阻塞,直接返回錯誤,因此可以在程序中添加判斷分支,用于處理獲取資源失敗之后的情況。
2.6.3 sem_timedwait
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
struct timespec {
time_t tv_sec; /* Seconds */
long tv_nsec; /* Nanoseconds [0 .. 999999999] */
};
2.6.4 sem_getvalue
int sem_getvalue(sem_t *sem, int *sval);
2.6.5 sem_post
int sem_post(sem_t *sem);
2.6.6 使用
例子1:
一個空位,不會出現(xiàn)問題,但是當(dāng)生產(chǎn)著剛開始給的value大時會出問題,因為訪問了公共資源。
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>
#include<semaphore.h>
struct Node
{
int number;
struct Node* next;
};
struct Node* head = NULL;
sem_t semp,semc;
void* consumerFunc(void *)
{
while(1)
{
//查看是否可以消費
sem_wait(&semc);
//消費
struct Node* node = head;
printf("消費者,id : %ld, number:%d\n",pthread_self(),node->number);
head = head->next;
free(node);
//消費結(jié)束,提示生產(chǎn)者,繼續(xù)生產(chǎn)
sem_post(&semp);
sleep(rand() % 3);
}
}
void* producerFuc(void *)
{
while(1)
{
//看是否可以生產(chǎn)
sem_wait(&semp);
//生產(chǎn)資源
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->number = rand() % 1000;
newNode->next = head;
head = newNode;
printf("生產(chǎn)者,id: %ld,number: %d\n",pthread_self(),newNode->number);
//提示消費者可以消費了
sem_post(&semc);
sleep(rand() % 3);
}
return NULL;
}
int main()
{
pthread_t consumer[5],producer[5];
//初始化信號量,只有一個空位供給生產(chǎn)消費
sem_init(&semp,0,1);
sem_init(&semc,0,0);
//創(chuàng)建線程
for(int i = 0; i < 5; i++)
{
pthread_create(&consumer[i],NULL,producerFuc,NULL);
}
for(int i = 0; i < 5; i++)
{
pthread_create(&producer[i],NULL,consumerFunc,NULL);
}
//阻塞回收
for(int i = 0; i < 5; i++)
{
pthread_join(consumer[i],NULL);
}
for(int i = 0; i < 5; i++)
{
pthread_join(producer[i],NULL);
}
//信號量釋放
sem_destroy(&semp);
sem_destroy(&semc);
return 0;
}
加鎖解決問題:文章來源:http://www.zghlxwxcb.cn/news/detail-432106.html
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>
#include<semaphore.h>
struct Node
{
int number;
struct Node* next;
};
struct Node* head = NULL;
sem_t semp,semc;
pthread_mutex_t mutex;
void* consumerFunc(void *)
{
while(1)
{
//查看是否可以消費
sem_wait(&semc);
pthread_mutex_lock(&mutex);
//加在 sem_wait下,避免死鎖
//消費
struct Node* node = head;
printf("消費者,id : %ld, number:%d\n",pthread_self(),node->number);
head = head->next;
free(node);
pthread_mutex_unlock(&mutex);
//消費結(jié)束,提示生產(chǎn)者,繼續(xù)生產(chǎn)
sem_post(&semp);
sleep(rand() % 3);
}
}
void* producerFuc(void *)
{
while(1)
{
//看是否可以生產(chǎn)
sem_wait(&semp);
pthread_mutex_lock(&mutex);
//生產(chǎn)資源
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->number = rand() % 1000;
newNode->next = head;
head = newNode;
printf("生產(chǎn)者,id: %ld,number: %d\n",pthread_self(),newNode->number);
pthread_mutex_unlock(&mutex);
//提示消費者可以消費了
sem_post(&semc);
sleep(rand() % 3);
}
return NULL;
}
int main()
{
pthread_t consumer[5],producer[5];
//初始化信號量,只有一個空位供給生產(chǎn)消費
sem_init(&semp,0,1);
sem_init(&semc,0,0);
//初始化mutex
pthread_mutex_init(&mutex,NULL);
//創(chuàng)建線程
for(int i = 0; i < 5; i++)
{
pthread_create(&consumer[i],NULL,producerFuc,NULL);
}
for(int i = 0; i < 5; i++)
{
pthread_create(&producer[i],NULL,consumerFunc,NULL);
}
//阻塞回收
for(int i = 0; i < 5; i++)
{
pthread_join(consumer[i],NULL);
}
for(int i = 0; i < 5; i++)
{
pthread_join(producer[i],NULL);
}
//信號量釋放
sem_destroy(&semp);
sem_destroy(&semc);
pthread_mutex_destroy(&mutex);
return 0;
}
加鎖是加在了sem_wait下,這樣可以避免死鎖,不然會出現(xiàn)一種情況:
A線程拿到了sem的資源,但是B線程先鎖定阻塞在了mutex位置,就會導(dǎo)致最后生產(chǎn)著無法生產(chǎn),消費者無法消費,死鎖了。文章來源地址http://www.zghlxwxcb.cn/news/detail-432106.html
到了這里,關(guān)于C多線程、鎖、同步、信號量的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!