linux线程全解
目錄
- 一、再論進(jìn)程
- 1、進(jìn)程的掛起、阻塞和睡眠的區(qū)別:
- 2、多進(jìn)程實現(xiàn)同時讀取鍵盤和鼠標(biāo)
- 二、線程的引入
- 1、線程進(jìn)程的區(qū)別體現(xiàn)在幾個方面
- 2、進(jìn)程與線程的選擇取決以下幾點(diǎn)
- 3、使用線程技術(shù)同時讀取鍵盤和鼠標(biāo)
- 三、線程常見函數(shù)
- 1、線程創(chuàng)建與回收
- 2、線程取消
- 3、線程函數(shù)退出相關(guān)
- 4、獲取線程id
- 四、線程同步之信號量
- 五、信號量相關(guān)函數(shù)
- 六、線程同步之互斥鎖
- 七、線程同步之條件變量
一、再論進(jìn)程
1、進(jìn)程的掛起、阻塞和睡眠的區(qū)別:
詳解:https://www.cnblogs.com/ck1020/p/6669661.html
(1)阻塞是一種被動的方式,由于獲取資源獲取不到而引起的等待。
(2)睡眠就是一種主動的方式,當(dāng)一個進(jìn)程獲取資源比如獲取最普通的鎖而失敗后,可以有兩種處理方式,1、自己睡眠,觸發(fā)調(diào)度;2、忙等待,使用完自己的時間。所以從這里看,睡眠的確是一種主動的方式,且僅僅作為一種處理手段。當(dāng)然睡眠不僅僅用于阻塞,更多的,我們可以在適當(dāng)?shù)臅r候設(shè)置讓進(jìn)程睡眠一定的時間,那么在這里,就可以發(fā)現(xiàn),睡眠之前,我們已經(jīng)預(yù)先規(guī)定了,你只能睡多長時間,這段時間過后,比必須返回來工作。
??睡眠都是由用戶控制的,睡眠恢復(fù)則是自動完成的,睡眠時間到了則恢復(fù)到就緒態(tài),睡眠時線程不會釋放對象鎖。
(3)掛起是由用戶控制的,掛起恢復(fù)需要用戶主動控制,掛起時線程不會釋放對象鎖。
(4)舉個例子,形象解釋一下三者的關(guān)系(引用自:https://blog.csdn.net/qq784515681/article/details/79142127)
??掛起線程的意思就是你對主動對雇工說:“你睡覺去吧,用著你的時候我主動去叫你,然后接著干活”。
??使線程睡眠的意思就是你主動對雇工說:“你睡覺去吧,某時某刻過來報到,然后接著干活”。
??線程阻塞的意思就是,你突然發(fā)現(xiàn),你的雇工不知道在什么時候沒經(jīng)過你允許,自己睡覺呢,但是你不能怪雇工,肯定你這個雇主沒注意,本來你讓雇工掃地,結(jié)果掃帚被偷了或被鄰居家借去了,你又沒讓雇工繼續(xù)干別的活,他就只好睡覺了。至于掃帚回來后,雇工會不會知道,會不會繼續(xù)干活,你不用擔(dān)心,雇工一旦發(fā)現(xiàn)掃帚回來了,他就會自己去干活的。因為雇工受過良好的培訓(xùn)。這個培訓(xùn)機(jī)構(gòu)就是操作系統(tǒng)。
2、多進(jìn)程實現(xiàn)同時讀取鍵盤和鼠標(biāo)
https://blog.csdn.net/u011508527/article/details/46878205
https://blog.csdn.net/jobbofhe/article/details/82192092
思路:創(chuàng)建子進(jìn)程,然后父子進(jìn)程分別進(jìn)行讀鍵盤和鼠標(biāo)的工作
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>int main(int argc, char *argv) {//先創(chuàng)建子進(jìn)程,然后父子進(jìn)程中分別進(jìn)行讀鍵盤和鼠標(biāo)的工作int ret = -1;int fd = -1;char buf[200] = {0};ret = fork();if (ret < 0){printf("fork error.\n");exit(-1);}else if (ret == 0){//子進(jìn)程fd = open("/dev/input/mouse0", O_RDONLY);if (fd < 0){perror("open");return -1;}while(1){memset(buf, 0, sizeof(buf));printf("before read mouse0\n");read(fd, buf, 50);printf("讀取的鼠標(biāo)的內(nèi)容是:[%s].\n", buf);}}else if (ret > 0){//父進(jìn)程while(1){memset(buf, 0, sizeof(buf));printf("before read keyboard.\n");read(0, buf, 50);printf("讀出的鍵盤內(nèi)容是:[%s]\n", buf);}}return 0; }??并發(fā):在操作系統(tǒng)中,是指一個時間段中有幾個程序都處于已啟動運(yùn)行到運(yùn)行完畢之間,且這幾個程序都是在同一個處理機(jī)上運(yùn)行,但任一個時刻點(diǎn)上只有一個程序在處理機(jī)上運(yùn)行。
操作系統(tǒng)并發(fā)程序執(zhí)行的特點(diǎn),并發(fā)環(huán)境下,由于程序的封閉性被打破,出現(xiàn)了新的特點(diǎn):
①程序與計算不再一一對應(yīng),一個程序副本可以有多個計算
②并發(fā)程序之間有相互制約關(guān)系,直接制約體現(xiàn)為一個程序需要另一個程序的計算結(jié)果,間接制約體現(xiàn)為多個程序競爭某一資源,如處理機(jī)、緩沖區(qū)等。
③并發(fā)程序在執(zhí)行中是走走停停,斷續(xù)推進(jìn)的。
3、使用進(jìn)程技術(shù)的優(yōu)勢
(1)CPU時分復(fù)用,單核心CPU可以實現(xiàn)宏觀上的并行
(2)實現(xiàn)多任務(wù)系統(tǒng)需求(多任務(wù)的需求是客觀的)
4、進(jìn)程技術(shù)的劣勢
(1)進(jìn)程間切換開銷大,進(jìn)程的切換包含了進(jìn)程斷點(diǎn)的保存與恢復(fù),有些類似于中斷及中斷返回
(2)進(jìn)程間通信麻煩而且效率低
5、解決方案就是線程技術(shù)
(1)線程技術(shù)保留了進(jìn)程技術(shù)實現(xiàn)多任務(wù)的特性。
(2)線程的改進(jìn)就是在線程間切換和線程間通信上提升了效率。
(3)多線程在多核心CPU上面更有優(yōu)勢。
二、線程的引入
詳解:https://www.cnblogs.com/yuanchenqi/articles/6755717.html
1、線程進(jìn)程的區(qū)別體現(xiàn)在幾個方面
??因為進(jìn)程擁有獨(dú)立的堆棧空間和數(shù)據(jù)段,所以每當(dāng)啟動一個新的進(jìn)程必須分配給它獨(dú)立的地址空間,建立眾多的數(shù)據(jù)表來維護(hù)它的代碼段、堆棧段和數(shù)據(jù)段,這對于多進(jìn)程來說十分“奢侈”,系統(tǒng)開銷比較大。
??而線程不一樣,線程擁有獨(dú)立的堆棧空間,但是共享數(shù)據(jù)段,它們彼此之間使用相同的地址空間,共享大部分?jǐn)?shù)據(jù),比進(jìn)程更節(jié)儉,開銷比較小,切換速度也比進(jìn)程快,效率高,但是正由于進(jìn)程之間獨(dú)立的特點(diǎn),使得進(jìn)程安全性比較高,也因為進(jìn)程有獨(dú)立的地址空間,一個進(jìn)程崩潰后,在保護(hù)模式下不會對其它進(jìn)程產(chǎn)生影響,而線程只是一個進(jìn)程中的不同執(zhí)行路徑。一個線程死掉就等于整個進(jìn)程死掉。(這句話可能有點(diǎn)問題,應(yīng)該在某種條件下是對的,比如單線程的進(jìn)程)
??當(dāng)一個線程死了(非正常退出、死循環(huán)等)就會導(dǎo)致線程該占有的資源永遠(yuǎn)無法釋放,從而影響其他線程的正常工作
(1)體現(xiàn)在通信機(jī)制上面,正因為進(jìn)程之間互不干擾,相互獨(dú)立,進(jìn)程的通信機(jī)制相對很復(fù)雜,譬如管道,信號,消息隊列,共享內(nèi)存,套接字等通信機(jī)制,而線程由于共享數(shù)據(jù)段所以通信機(jī)制很方便。
(2)屬于同一個進(jìn)程的所有線程共享該進(jìn)程的所有資源,包括文件描述符。而不同的進(jìn)程相互獨(dú)立。
(3)線程又稱為輕量級進(jìn)程,進(jìn)程有進(jìn)程控制塊,線程有線程控制塊;
(4)線程必定也只能屬于一個進(jìn)程,而進(jìn)程可以擁有多個線程而且至少擁有一個線程;
(5)體現(xiàn)在程序結(jié)構(gòu)上,舉一個簡明易懂的列子:當(dāng)我們使用進(jìn)程的時候,我們不自主的使用if else嵌套來判斷pid,使得程序結(jié)構(gòu)繁瑣,但是當(dāng)我們使用線程的時候,基本上可以甩掉它,當(dāng)然程序內(nèi)部執(zhí)行功能單元需要使用的時候還是要使用,所以線程對程序結(jié)構(gòu)的改善有很大幫助。
2、進(jìn)程與線程的選擇取決以下幾點(diǎn)
(1)需要頻繁創(chuàng)建銷毀的優(yōu)先使用線程;因為對進(jìn)程來說創(chuàng)建和銷毀一個進(jìn)程代價是很大的。
(2)線程的切換速度快,所以在需要大量計算,切換頻繁時用線程,還有耗時的操作使用線程可提高應(yīng)用程序的響應(yīng)
(3)因為對CPU系統(tǒng)的效率使用上線程更占優(yōu),所以可能要發(fā)展到多機(jī)分布的用進(jìn)程,多核分布用線程;
(4)并行操作時使用線程,如C/S 的服務(wù)器端并發(fā)線程響應(yīng)用戶的請求;
(5)需要更穩(wěn)定安全時,適合選擇進(jìn)程;需要速度時,選擇線程更好。
3、使用線程技術(shù)同時讀取鍵盤和鼠標(biāo)
#include <pthread.h> int pthread_create(pthread_t *restrict tidp, //新創(chuàng)建的線程ID指向的內(nèi)存單元。const pthread_attr_t *restrict attr, //線程屬性,默認(rèn)為NULLvoid *(*start_rtn)(void *), //新創(chuàng)建的線程從start_rtn函數(shù)的地址開始運(yùn)行void *restrict arg //默認(rèn)為NULL。若上述函數(shù)需要參數(shù),將參數(shù)放入結(jié)構(gòu)體中并將地址作為arg傳入。);restrict,C語言中的一種類型限定符,用于告訴編譯器,對象已經(jīng)被指針?biāo)?#xff0c;不能通過除該指針外所有其他直接或間接的方式修改該對象的內(nèi)容。pthread_create是(Unix、Linux、Mac OS X)等操作系統(tǒng)的創(chuàng)建線程的函數(shù)。它的功能是創(chuàng)建線程(實際上就是確定調(diào)用該線程函數(shù)的入口點(diǎn)),在線程創(chuàng)建以后,就開始運(yùn)行相關(guān)的線程函數(shù)。pthread_create的返回值表示成功,返回0;表示出錯,返回表示-1。編譯時需加上 -lpthread選項,使其能找到相應(yīng)的動態(tài)鏈接庫。 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <pthread.h>char buf[200] = {0};void *func(void *arg) {while(1){memset(buf, 0, sizeof(buf));printf("before read keyboard.\n");read(0, buf, 50);printf("讀取的鍵盤的內(nèi)容是:[%s].\n", buf);}}int main(int argc, char *argv[]) {int ret = -1;int fd = -1;pthread_t th = -1;ret = pthread_create(&th, NULL, func, NULL);if (ret != 0){printf("pthread_create error.\n");return -1;}fd = open("/dev/input/mouse0", O_RDONLY);if (fd < 0){perror("open error");exit(-1);}while(1){memset(buf, 0, sizeof(buf));printf("before read mouse0.\n");read(fd, buf, 50);printf("讀取的鼠標(biāo)的內(nèi)容是:[%s].\n", buf);}return 0; }4、linux中的線程簡介
(1)一種輕量級進(jìn)程
(2)線程是參與內(nèi)核調(diào)度的最小單元,操作系統(tǒng)工作時調(diào)度的單位
(3)一個進(jìn)程中可以有多個線程,線程依附于進(jìn)程而存在。進(jìn)程一死,線程就全都結(jié)束了。一個進(jìn)程中多個線程,某個線程死了,其他線程仍可存活。
5、線程技術(shù)的優(yōu)勢
https://www.cnblogs.com/valjeanshaw/p/11469514.html
(1)像進(jìn)程一樣可被OS調(diào)度
(2)同一進(jìn)程的多個線程之間很容易高效率通信,進(jìn)程中的線程類似于一個程序中的不同函數(shù),進(jìn)程類似于兩個程序。
(3)在多核心CPU(對稱多處理器架構(gòu)SMP)架構(gòu)下效率最大化
??操作系統(tǒng)的實現(xiàn)方法無法保證多個進(jìn)程在多個CPU核心上運(yùn)行,而多線程卻可以,可以保證,例如兩個線程在兩個核心上運(yùn)行,當(dāng)線程數(shù)超過CPU核心數(shù)則又會出現(xiàn)分時復(fù)用。
??核心數(shù)多的電腦不一定比核心數(shù)少的電腦運(yùn)行的快,如果分別為4/8核電腦,而線程只有四個,則二者速度相同,若是8個線程,則8核心電腦的運(yùn)行速度快。
6、并行處理是計算機(jī)系統(tǒng)中能同時執(zhí)行兩個或更多個處理的一種計算方法。并行處理可同時工作于同一程序的不同方面。并行處理的主要目的是節(jié)省大型和復(fù)雜問題的解決時間。
??并發(fā)處理:指一個時間段中有幾個程序都處于已啟動運(yùn)行到運(yùn)行完畢之間,且這幾個程序都是在同一個處理機(jī)(CPU)上運(yùn)行,但任一個時刻點(diǎn)上只有一個程序在處理機(jī)(CPU)上運(yùn)行。
??并發(fā)的關(guān)鍵是你有處理多個任務(wù)的能力,不一定要同時。并行的關(guān)鍵是你有同時處理多個任務(wù)的能力。所以說,并行是并發(fā)的子集.
三、線程常見函數(shù)
Linux下的多線程遵從POSIX線程接口,簡稱pthread,在pthread庫中提供
1、線程創(chuàng)建與回收
(1)pthread_create 主線程用來創(chuàng)造子線程的
#include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);(2)pthread_join 主線程用來等待(阻塞)回收子線程
#include <pthread.h> int pthread_join(pthread_t thread, void **retval);??pthread_join()函數(shù)會一直阻塞調(diào)用線程,直到指定的線程終止。當(dāng)pthread_join()返回之后,應(yīng)用程序可回收與已終止線程關(guān)聯(lián)的任何數(shù)據(jù)存儲空間。但是,同時需要注意,一定要和上面創(chuàng)建的某一線程配套使用,這樣還可以起到互斥的作用。否則多線程可能搶占CPU資源,導(dǎo)致運(yùn)行結(jié)果不確定。
(3)pthread_detach 主線程用來分離子線程,分離后主線程不必再去回收子線程,子線程返回系統(tǒng)時自動回收。
??在默認(rèn)情況下通過pthread_create函數(shù)創(chuàng)建的線程是非分離屬性的,由pthread_create函數(shù)的第二個參數(shù)決定,在非分離的情況下,當(dāng)一個線程結(jié)束的時候,它所占用的系統(tǒng)資源并沒有完全真正的釋放,也沒有真正終止。
??只有在pthread_join函數(shù)返回時,該線程才會釋放自己的資源。或者是設(shè)置在分離屬性的情況下,一個線程結(jié)束會立即釋放它所占用的資源。
??如果要保證創(chuàng)建線程之后,確保無內(nèi)存泄漏,必須采用如下方法來規(guī)范pthread_create的使用:設(shè)置線程是detached(分離屬性的)。
void pthread_exit( void * value_ptr ); 線程的終止可以是調(diào)用了pthread_exit或者該線程的例程結(jié)束。也就是說,一個線程可以隱式的退出,也可以顯式的調(diào)用pthread_exit函數(shù)來退出。 pthread_exit函數(shù)唯一的參數(shù)value_ptr是函數(shù)的返回代碼,只要pthread_join中的第二個參數(shù)value_ptr不是NULL,這個值將被傳遞給value_ptr。 函數(shù)原型如下: int pthread_join( pthread_t thread, void * * value_ptr ); 函數(shù)pthread_join的作用是,等待一個線程終止。 調(diào)用pthread_join的線程將被掛起直到參數(shù)thread所代表的線程終止時為止。pthread_join是一個線程阻塞函數(shù),調(diào)用它的函數(shù)將一直等到被等待的線程結(jié)束為止。 如果value_ptr不為NULL,那么線程thread的返回值存儲在該指針指向的位置。該返回值可以是由pthread_exit給出的值,或者該線程被取消而返回PTHREAD_CANCELED。2、線程取消
詳解:https://blog.csdn.net/qq_40399012/article/details/84255522
(1)pthread_cancel 一般都是主線程調(diào)用該函數(shù)去取消(讓它趕緊死)子線程
#include <pthread.h> int pthread_cancel(pthread_t thread);??函數(shù)只是給線程發(fā)送了一個請求該請求是希望可以將該線程終止。所以對于該請求的話,只是對于線程的一個建議。線程也可能就不會立即終止,會繼續(xù)運(yùn)行,直到運(yùn)行到取消點(diǎn)的時候該線程才會退出
(2)pthread_setcancelstate 子線程設(shè)置自己是否允許被取消
#include <pthread.h> int pthread_setcancelstate(int state, int *oldstate);參數(shù):state:將當(dāng)前狀態(tài)改為stateoldstate:將該線程原先的狀態(tài)放到oldtype所指向的空間里面返回值:成功返回0,失敗返回錯誤碼??這兩步是一個原子操作。
??對于pthread_cancel函數(shù)默認(rèn)的是PTHREAD_CANCEL_ENABLE可取消狀態(tài)。當(dāng)狀態(tài)設(shè)為PTHREAD_CANCEL_DISABLE時,對于pthread_cancel的調(diào)用并不會殺死線程。相反,該取消請求對于這個線程還處于掛起狀態(tài)(也就是未決),直到線程的取消狀態(tài)變?yōu)镻THREAD_CANCEL_ENABLE時,線程將在下一個取消點(diǎn)(取消點(diǎn)是線程檢查他是否被取消的一個位置)上對所有的掛起請求進(jìn)行處理。
(3)pthread_setcanceltype 設(shè)置退出的類型,只有(2)設(shè)置為允許,(3)才有效
#include <pthread.h> int pthread_setcanceltype(int type, int *oldtype);參數(shù):type:將取消的類型設(shè)置為typeoldtype:將該線程的取消類型放到oldtype所指向的空間里面 返回值:成功返回0,失敗返回錯誤碼??我們默認(rèn)的取消類型是推遲取消。也就是會運(yùn)行到取消點(diǎn)再取消。對于可以設(shè)置的取消類型有PTHREAD_CANCEL_DEFERRED(推遲取消),也就是默認(rèn)的取消類型。PTHRAED_CANCEL_ASYNCHRONOUS(異步取消),采用異步取消之后,線程可以在任意時間取消,不是非得到取消點(diǎn)才可以取消。
3、線程函數(shù)退出相關(guān)
詳解:https://blog.csdn.net/longbei9029/article/details/72871714
(1)pthread_exit與return退出,普通情況一樣。某些情況下不同,不可使用exit()函數(shù)退出,該函數(shù)會結(jié)束進(jìn)程。導(dǎo)致其他線程也收到影響。
(2)pthread_cleanup_push
(3)pthread_cleanup_pop
#include <pthread.h> void pthread_cleanup_push(void (*routine)(void *),void *arg);//把用來清理的函數(shù)壓棧 void pthread_cleanup_pop(int execute);//把用來清理的函數(shù)彈棧??線程可以安排他退出時需要調(diào)用的函數(shù),這與進(jìn)程可以用atexit函數(shù)安排進(jìn)程退出時需要調(diào)用的函數(shù)是類似的。這樣的函數(shù)稱為線程清理處理程序,線程可以建立多個清理處理程序。處理程序記錄在棧中,也就是說他們的執(zhí)行順序與他們注冊的順序相反。
?(2)、(3)與線程同步有關(guān),主線程與子線程或者兩三個線程同步有關(guān)。線程同步時需要用到鎖。
int cnt = 0;//0表示鎖打開if (cnt == 0) {cnt++;//鎖關(guān)閉了 pthread_cleanup_push(func, arg);pthread_cleanup_push(func1, arg);//子線程操作//子線程有可能在這里被主線程取消,但未來的及把鎖打開,導(dǎo)致其他線程無法進(jìn)入//所以要想辦法在子線程死后,鎖不會繼續(xù)鎖住pthread_cleanup_pop(0);//0將壓棧的函數(shù)取出不執(zhí)行,1表示取出并執(zhí)行pthread_cleanup_pop(0);//壓棧幾個就要調(diào)用函數(shù)幾次取出cnt--;//鎖打開 }void func(void *arg) {cnt--; }4、獲取線程id
#include <pthread.h> pthread_t pthread_self(void);描述: 函數(shù)的作用是:返回調(diào)用線程的ID。這是相同的值返回值: 這個函數(shù)總是成功的,返回調(diào)用線程的ID。summary:這些函數(shù)開頭的P表示一個標(biāo)準(zhǔn):可移植操作系統(tǒng)接口(英語:Portable Operating System Interface,縮寫為POSIX),而定義API的一系列互相關(guān)聯(lián)的標(biāo)準(zhǔn)的總稱,其正式稱呼為IEEE Std 1003,而是IEEE為要在各種UNIX操作系統(tǒng)上運(yùn)行軟件國際標(biāo)準(zhǔn)名稱為ISO/IEC 9945。
四、線程同步之信號量
1、任務(wù):用戶從終端輸入任意字符然后統(tǒng)計個數(shù)顯示,輸入end則結(jié)束
??使用多線程實現(xiàn):主線程獲取用戶輸入并判斷是否退出,子線程計數(shù)
(1)為什么需要多線程實現(xiàn)
(2)問題和困難點(diǎn)是?
(3)理解什么是線程同步
詳解:https://blog.csdn.net/qq_22847457/article/details/89430008
???https://www.cnblogs.com/yinbiao/p/11190336.html
2、同步概念
??所謂同步,即同時起步,協(xié)調(diào)一致。不同的對象,對“同步”的理解方式略有不同。如,設(shè)備同步,是指在兩個設(shè)備之間規(guī)定一個共同的時間參考;數(shù)據(jù)庫同步,是指讓兩個或多個數(shù)據(jù)庫內(nèi)容保持一致,或者按需要部分保持一致;文件同步,是指讓兩個或多個文件夾里的文件保持一致。而編程中、通信中所說的同步與生活中大家印象中的同步概念略有差異。“同”字應(yīng)是指協(xié)同、協(xié)助、互相配合。主旨在協(xié)同步調(diào),按預(yù)定的先后次序運(yùn)行。
3、線程同步
??同步即協(xié)同步調(diào),按預(yù)定的先后次序運(yùn)行。
??線程同步,指一個線程發(fā)出某一功能調(diào)用時,在沒有得到結(jié)果之前,該調(diào)用不返回。同時其它線程為保證數(shù)據(jù)一致性,不能調(diào)用該功能。
舉例1: 銀行存款 5000。柜臺,折:取3000;提款機(jī),卡:取 3000。剩余:2000
舉例2:內(nèi)存中100字節(jié),線程T1欲填入全1,線程T2欲填入全0。但如果T1執(zhí)行了50個字節(jié)失去cpu,T2執(zhí)行,會將T1寫過的內(nèi)容覆蓋。當(dāng)T1再次獲得cpu繼續(xù)從失去cpu的位置向后寫入1,當(dāng)執(zhí)行結(jié)束,內(nèi)存中的100字節(jié),既不是全1,也不是全0。
例1、2產(chǎn)生的現(xiàn)象叫做“與時間有關(guān)的錯誤”。為了避免這種數(shù)據(jù)混亂,線程需要同步。“同步”的目的,是為了避免數(shù)據(jù)混亂,解決與時間有關(guān)的錯誤。實際上,不僅線程間需要同步,進(jìn)程間、信號間等等都需要同步機(jī)制。因此,所有“多個控制流,共同操作一個共享資源”的情況,都需要同步。
4、數(shù)據(jù)混亂原因
1)資源共享(獨(dú)享資源則不會)
2)調(diào)度隨機(jī)(意味著數(shù)據(jù)訪問會出現(xiàn)競爭)
3)線程間缺乏必要的同步機(jī)制。
??以上3點(diǎn)中,前兩點(diǎn)不能改變,欲提高效率,傳遞數(shù)據(jù),資源必須共享。只要共享資源,就一定會出現(xiàn)競爭。只要存在競爭關(guān)系,數(shù)據(jù)就很容易出現(xiàn)混亂。所以只能從第三點(diǎn)著手解決。使多個線程在訪問共享資源的時候,出現(xiàn)互斥。
5、信號量的介紹和使用
(1)信號:(signal)是一種處理異步事件的方式。信號是比較復(fù)雜的通信方式,用于通知接受進(jìn)程有某種事件發(fā)生,除了用于進(jìn)程外,還可以發(fā)送信號給進(jìn)程本身。
(2)信號量:(Semaphore)進(jìn)程間通信處理同步互斥的機(jī)制。是在多線程環(huán)境下使用的一種設(shè)施,它負(fù)責(zé)協(xié)調(diào)各個線程, 以保證它們能夠正確、合理的使用公共資源。
??信號量是一個特殊類型的變量,它可以被增加或減少,但對其的關(guān)鍵訪問被保證是原子操作,即使在一個多線程程序中也是如此。這意味著如果一個程序中有兩個(或更多)的線程試圖改變一個信號量的值,系統(tǒng)將保證所有的操作都將依次進(jìn)行。但如果是普通變量,來自同一程序中不同線程的沖突操作所導(dǎo)致的結(jié)果將是不確定的。
??最簡單的信號量是二進(jìn)制信號量,它只有0和1兩種取值。還有更通用的信號量——計數(shù)信號量,它可以有更大的取值范圍。信號量一般常用來保護(hù)一段代碼,使其每次只能被一個執(zhí)行線程運(yùn)行,要完成這個工作,就要使用二進(jìn)制信號量。有時,我們希望可以允許有限數(shù)目的線程執(zhí)行一段指定的代碼,這就需要計數(shù)信號量。
(3)信號量和信號的區(qū)別
??簡單地說,信號就是一種異步通信,通知進(jìn)程某種事件的發(fā)生;信號量是進(jìn)程/線程同步與互斥的一種機(jī)制,保證進(jìn)程/線程間之間的有序執(zhí)行或?qū)操Y源的有序訪問。
五、信號量相關(guān)函數(shù)
詳解:https://blog.csdn.net/yishizuofei/article/details/78213108
???更多詳情請參考man手冊
??信號量函數(shù)的名字都以sem_開頭,信號量類型是sem_t,線程中使用的基本信號量函數(shù)有4個。分別是:一個創(chuàng)建,兩個控制,一個清理
1、信號量創(chuàng)建函數(shù)
#include <semaphore.h> int sem_init(sem_t *sem,int pshared,usigned int value);參數(shù):sem_init()。value參數(shù)指定信號量的初始值.pshared參數(shù)指明信號量是由進(jìn)程內(nèi) 線程共享,還是由進(jìn)程之間共享。如果pshared的值初始化一個定位在sem的匿名信號量為0, 那么信號量將被進(jìn)程內(nèi)的線程共享,并且應(yīng)該放置在所有線程都可見的地址上(如全局變量, 或者堆上動態(tài)分配的變量)。如果pshared是非零值,那么信號量將在進(jìn)程之間共享,并且應(yīng)該定位共享內(nèi)存區(qū)域 (見 shm_open(3)、mmap(2) 和 shmget(2)).(因為通過fork(2)創(chuàng)建的孩子繼承其父親的 內(nèi)存映射,因此它也可以見到這個信號量。)所有可以訪問共享內(nèi)存區(qū)域的進(jìn)程都可以使用sem_post(3)、sem_wait(3) 等等操作信號量。初始化一個已經(jīng)初始的信號量其結(jié)果未定義。返回值:sem_init() 成功時返回 0;錯誤時,返回 -1,并把 errno 設(shè)置為合適的值。錯誤:EINVALvalue 超過 SEM_VALUE_MAX。ENOSYSpshared 非零,但系統(tǒng)還沒有支持進(jìn)程共享的信號量。int sem_trywait(sem_t *sem);//sem_wait的非阻塞版本sem_trywait函數(shù)是sem_wait的非阻塞版本,sem_trywait()函數(shù)僅在信號量當(dāng)前沒有鎖定 的情況下(也就是說,如果信號量值是正的),鎖定由sem所引用的信號量;否則,它就不能鎖定 信號量。如果調(diào)用進(jìn)程成功地執(zhí)行了由sem指定的信號量鎖操作,那么sem_trywait()和 sem_wait()函數(shù)將返回零。如果調(diào)用不成功,信號量的狀態(tài)將保持不變,函數(shù)將返回-1的值, 并設(shè)置errno來指示錯誤。2、信號量控制函數(shù)
#include <semaphore.h> int sem_wait(sem_t *sem);參數(shù):sem_wait函數(shù)也是一個原子操作,它的作用是從信號量的值減去一個“1”,但它永遠(yuǎn)會先 等待該信號量為一個非零值才開始做減法。也就是說,如果你對一個值為2的信號量調(diào) 用sem_wait(),線程將會繼續(xù)執(zhí)行,這信號量的值將減到1。如果對一個值為0的信號量調(diào)用 sem_wait(),這個函數(shù)就會地等待直到有其它線程增加了這個值使它不再是0為止。如果有兩 個線程都在sem_wait()中等待同一個信號量變成非零值,那么當(dāng)它被第三個線程增加一個 “1”時,等待線程中只有一個能夠?qū)π盘柫孔鰷p法并繼續(xù)執(zhí)行,另一個還將處于等待狀態(tài)。返回值:所有這些函數(shù)在成功時都返回 0;錯誤保持信號量值沒有更改,-1 被返回,并設(shè)置 errno 來指明錯誤。錯誤:EINTR這個調(diào)用被信號處理器中斷,EINVALsem 不是一個有效的信號量。int sem_post(sem_t *sem);參數(shù):sem_post函數(shù)的作用是給信號量的值加上一個“1”,它是一個“原子操作”---即同時對 同一個信號量做加“1”操作的.兩個線程是不會沖突的;而同時對同一個文件進(jìn)行讀、加和寫操作的兩個程序就有可能會引起 沖突。信號量的值永遠(yuǎn)會正確地加一個“2”--因為有兩個線程試圖改變它。函數(shù)sem_post()用來增加信號量的值。當(dāng)有線程阻塞在這個信號量上時,調(diào)用這個函數(shù)會使其 中的一個線程不在阻塞,選擇機(jī)制同樣是由線程的調(diào)度策略決定的。函數(shù)sem_wait()被用來阻 塞當(dāng)前線程直到信號量sem的值大于0,解除阻塞后將sem的值減一返回值:sem_post() 成功時返回 0;錯誤時,信號量的值沒有更改,-1 被返回,并設(shè)置 errno 來指明錯誤。錯誤:EINVALsem 不是一個有效的信號量。EOVERFLOW信號量允許的最大值將要被超過。3、信號量清理函數(shù)
#include <semaphore.h> int sem_destroy(sem_t *sem);與前幾個函數(shù)一樣,這個函數(shù)也以一個信號量指針為參數(shù),并清理該信號量擁有的所有資源。 如果企圖清理的信號量正在被一些線程等待,就會收到一個錯誤。函數(shù)在成功時會返回0。 #include <stdio.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h>char buf[200] = {0}; sem_t sem; unsigned int flag = 0;//子線程程序,作用統(tǒng)計buf中的字符個數(shù)并打印 void *func(void *arg) {//子線程首先應(yīng)該有個循環(huán)//循環(huán)中阻塞在等待主線程激活的時候,子線程被激活后就去獲取buf中的字符//長度,然后打印,完成后再次被阻塞sem_wait(&sem);//等待信號量,如果信號量的值大于0,將信號量的值減1,//立即返回。如果信號量的值為0,則線程阻塞。相當(dāng)于//P操作。成功返回0,失敗返回-1。while(flag == 0){printf("本次輸入了%ld個字符.\n", strlen(buf));memset(buf, 0, sizeof(buf));sem_wait(&sem); }pthread_exit(NULL); }int main(int argc, char *argv[]) {int ret = -1;pthread_t th= -1; sem_init(&sem, 0, 0);ret = pthread_create(&th, NULL, func, NULL);if (ret != 0){printf("pthread_create error.\n");exit(-1);}printf("輸入一個字符串,以回車鍵結(jié)束.\n");while(scanf("%s", buf)){//判斷輸入的是不是end,若為end結(jié)束程序if (!strcmp(buf, "end")){printf("輸入的是end,程序結(jié)束.\n");flag = 1;sem_post(&sem);exit(-1);}//主線程在收到用戶輸入的字符串,并且確認(rèn)不是end后//就去發(fā)信號激活子線程來計數(shù)//子線程被阻塞,主線程可以激活,這就是線程同步問題//信號量就可以用來實現(xiàn)這個線程同步sem_post(&sem); }//回收子線程printf("等待回收子線程.\n");ret = pthread_join(th, NULL);if (ret != 0){printf("pthread_join error.\n");exit(-1);}printf("子線程回收成功.\n");sem_destroy(&sem);return 0; }六、線程同步之互斥鎖
1、什么是互斥鎖
(1)互斥鎖又叫互斥量(mutex)
??Linux中提供一把互斥鎖mutex(也稱之為互斥量)。每個線程在對資源操作前都嘗試先加鎖,成功加鎖才能操作,操作結(jié)束解鎖。
??資源還是共享的,線程間也還是競爭的,但通過“鎖”就將資源的訪問變成互斥操作,而后與時間有關(guān)的錯誤也不會再產(chǎn)生了。但應(yīng)注意:同一時刻,只能有一個線程持有該鎖。
??當(dāng)A線程對某個全局變量加鎖訪問,B在訪問前嘗試加鎖,拿不到鎖,B阻塞。C線程不去加鎖,而直接訪問該全局變量,依然能夠訪問,但會出現(xiàn)數(shù)據(jù)混亂。互斥鎖實質(zhì)上是操作系統(tǒng)提供的一把“建議鎖”(又稱“協(xié)同鎖”),建議程序中有多線程訪問共享資源的時候使用該機(jī)制。但并沒有強(qiáng)制限定。
??因此,即使有了mutex,如果有線程不按規(guī)則來訪問數(shù)據(jù),依然會造成數(shù)據(jù)混亂.
(2)何謂自旋鎖?
??它是為實現(xiàn)保護(hù)共享資源而提出一種鎖機(jī)制。其實,自旋鎖與互斥鎖比較類似,它們都是為了解決對某項資源的互斥使用。
??無論是互斥鎖,還是自旋鎖,在任何時刻,最多只能有一個保持者,也就說,在任何時刻最多只能有一個執(zhí)行單元獲得鎖。但是兩者在調(diào)度機(jī)制上略有不同。
??對于互斥鎖,如果資源已經(jīng)被占用,資源申請者只能進(jìn)入睡眠狀態(tài)。但是自旋鎖不會引起調(diào)用者睡眠,如果自旋鎖已經(jīng)被別的執(zhí)行單元保持,調(diào)用者就一直循環(huán)在那里看是否該自旋鎖的保持者已經(jīng)釋放了鎖,"自旋"一詞就是因此而得名。
詳解:https://blog.csdn.net/qq_20817327/article/details/108743635
(3)相關(guān)函數(shù)
詳解:https://blog.csdn.net/qq_22847457/article/details/89430008
(4)互斥鎖和信號量的關(guān)系:可以認(rèn)為互斥鎖是一種特殊的信號量,值只能是0和1的信號量
(5)互斥鎖主要用來實現(xiàn)關(guān)鍵段保護(hù)
2、用互斥鎖來實現(xiàn)上小節(jié)的代碼
注意:man 3 pthread_mutex_init時提示找不到函數(shù),說明你沒有安裝pthread相關(guān)的man手冊。
安裝方法:1、虛擬機(jī)上網(wǎng);2、sudo apt-get install manpages-posix-dev
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h>char buf[200] = {0}; pthread_mutex_t mutex; unsigned int flag = 0;//子線程等待程序,作用是統(tǒng)計buf中的字符個數(shù)并打印 void *func(void *arg) {//子線程首先應(yīng)該有個循環(huán)//循環(huán)中阻塞在等待主線程激活的時候,子線程被激活后就去獲取buf中的字符//長度,然后打印,完成后再次被阻塞sleep(1);while(flag == 0){pthread_mutex_lock(&mutex);printf("本次輸入了%ld個字符.\n", strlen(buf));memset(buf, 0, sizeof(buf));pthread_mutex_unlock(&mutex);sleep(1);}pthread_exit(NULL); }int main(int argc, char *argv[]) {int ret = -1;pthread_t th = -1;pthread_mutex_init(&mutex, NULL);ret = pthread_create(&th, NULL, func, NULL);if (ret != 0){printf("pthread_create error.\n");}printf("輸入一個字符串,以回車結(jié)束\n");while(1){pthread_mutex_lock(&mutex);scanf("%s", buf);pthread_mutex_unlock(&mutex);//去比較用戶輸入的是不是endif (!strncmp(buf, "end", 3)){printf("程序結(jié)束.\n");flag = 1;break;}sleep(1);}//回收子線程printf("等待回收子線程.\n");ret = pthread_join(th, NULL);if (ret != 0){printf("pthread_join error.\n");exit(-1);}printf("子線程回收成功.\n");pthread_mutex_destroy(&mutex);return 0; }七、線程同步之條件變量
詳解:https://www.cnblogs.com/liangf27/p/9493722.html
? ? https://blog.csdn.net/weixin_33985507/article/details/86037200
1、什么是條件變量
??條件變量使我們可以睡眠等待某種條件出現(xiàn)。條件變量是利用線程間共享的全局變量進(jìn)行同步的一種機(jī)制,主要包括兩個動作:一個線程等待"條件變量的條件成立"而掛起;另一個線程使"條件成立"(給出條件成立信號)。
??條件的檢測是在互斥鎖的保護(hù)下進(jìn)行的。如果一個條件為假,一個線程自動阻塞,并釋放等待狀態(tài)改變的互斥鎖。如果另一個線程改變了條件,它發(fā)信號給關(guān)聯(lián)的條件變量,喚醒一個或多個等待它的線程,重新獲得互斥鎖,重新評價條件。如果兩進(jìn)程共享可讀寫的內(nèi)存,條件變量可以被用來實現(xiàn)這兩進(jìn)程間的線程同步。
??使用條件變量之前要先進(jìn)行初始化。可以在單個語句中生成和初始化一個條件變量如:
pthread_cond_t my_condition=PTHREAD_COND_INITIALIZER; //(用于進(jìn)程間線程的通信)。可以利用函數(shù)pthread_cond_init動態(tài)初始化。??條件變量分為兩部分: 條件和變量.。條件本身是由互斥量保護(hù)的,線程在改變條件狀態(tài)前先要鎖住互斥量。它利用線程間共享的全局變量進(jìn)行同步的一種機(jī)制。
??互斥鎖是用來給資源上鎖的,而條件變量是用來等待而不是用來上鎖的。條件變量用來自動阻塞一個線程,直到某特殊情況發(fā)生為止。通常條件變量和互斥鎖同時使用。
2、相關(guān)函數(shù)
pthread_cond_init pthread_cond_destroy pthread_cond_wait pthread_cond_signal/pthread_cond_broadcast(1) 初始化:
?? 條件變量采用的數(shù)據(jù)類型是 pthread_cond_t, 在使用之前必須要進(jìn)行初始化, 這包括兩種方式:
靜態(tài): 可以把常量PTHREAD_COND_INITIALIZER給靜態(tài)分配的條件變量.
動態(tài): pthread_cond_init函數(shù), 釋放動態(tài)條件變量的內(nèi)存空間之前, 要用pthread_cond_destroy對其進(jìn)行清理.
(2)等待條件:
#include <pthread.h>int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restric mutex); int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict timeout);成功則返回0, 出錯則返回錯誤編號.這兩個函數(shù)分別是阻塞等待和超時等待.等待條件函數(shù)等待條件變?yōu)檎?span id="ze8trgl8bvbq" class="token punctuation">, 傳遞給pthread_cond_wait的互斥量對條件進(jìn)行保護(hù), 調(diào)用 者把鎖住的互斥量傳遞給函數(shù). 函數(shù)把調(diào)用線程放到等待條件的線程列表上, 然后對互斥量 解鎖, 這兩個操作是原子的. 這樣便關(guān)閉了條件檢查和線程進(jìn)入休眠狀態(tài)等待條件改變這兩 個操作之間的時間通道, 這樣線程就不會錯過條件的任何變化.當(dāng)pthread_cond_wait返回時, 互斥量再次被鎖住.(3)通知條件:
#include <pthread.h>int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *cond);成功則返回0, 出錯則返回錯誤編號.這兩個函數(shù)用于通知線程條件已經(jīng)滿足. 調(diào)用這兩個函數(shù), 也稱向線程或條件發(fā)送信號. 必須注意, 一定要在改變條件狀態(tài)以后再給線程發(fā)送信號.3、使用條件變量來實現(xiàn)上小節(jié)代碼
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h>char buf[200] = {0}; pthread_mutex_t mutex; pthread_cond_t cond; unsigned int flag = 0;//子線程等待程序,作用是統(tǒng)計buf中的字符個數(shù)并打印 void *func(void *arg) {//子線程首先應(yīng)該有個循環(huán)//循環(huán)中阻塞在等待主線程激活的時候,子線程被激活后就去獲取buf中的字符//長度,然后打印,完成后再次被阻塞while(flag == 0){pthread_mutex_lock(&mutex);pthread_cond_wait(&cond, &mutex);printf("本次輸入了%ld個字符.\n", strlen(buf));memset(buf, 0, sizeof(buf));pthread_mutex_unlock(&mutex);sleep(1); }pthread_exit(NULL); }int main(int argc, char *argv[]) {int ret = -1;pthread_t th = -1;pthread_mutex_init(&mutex, NULL);pthread_cond_init(&cond, NULL);ret = pthread_create(&th, NULL, func, NULL);if (ret != 0){printf("pthread_create error.\n");}printf("輸入一個字符串,以回車結(jié)束\n");while(1){scanf("%s", buf);pthread_cond_signal(&cond);//去比較用戶輸入的是不是endif (!strncmp(buf, "end", 3)){printf("程序結(jié)束.\n");flag = 1;break;}sleep(1);}//回收子線程printf("等待回收子線程.\n");ret = pthread_join(th, NULL);if (ret != 0){printf("pthread_join error.\n");exit(-1);}printf("子線程回收成功.\n");pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);return 0; }4、線程同步總結(jié)
(1)條件變量與互斥鎖、信號量的區(qū)別:
(2)為什么有了互斥鎖還要有條件變量?
?? 詳解:https://blog.csdn.net/weixin_43812622/article/details/97389712
注:本資料大部分由朱老師物聯(lián)網(wǎng)大講堂課程筆記整理而來并且引用了部分他人博客的內(nèi)容,如有侵權(quán),聯(lián)系刪除!水平有限,如有錯誤,歡迎各位在評論區(qū)交流。
總結(jié)
- 上一篇: 运维工作常见问题处理38-74(二)
- 下一篇: Unity3D渲染系列之SkyBox天空