日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

linux线程全解

發(fā)布時間:2023/12/31 linux 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 linux线程全解 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

目錄

  • 一、再論進程
    • 1、進程的掛起、阻塞和睡眠的區(qū)別:
    • 2、多進程實現(xiàn)同時讀取鍵盤和鼠標
  • 二、線程的引入
    • 1、線程進程的區(qū)別體現(xiàn)在幾個方面
    • 2、進程與線程的選擇取決以下幾點
    • 3、使用線程技術(shù)同時讀取鍵盤和鼠標
  • 三、線程常見函數(shù)
    • 1、線程創(chuàng)建與回收
    • 2、線程取消
    • 3、線程函數(shù)退出相關(guān)
    • 4、獲取線程id
  • 四、線程同步之信號量
  • 五、信號量相關(guān)函數(shù)
  • 六、線程同步之互斥鎖
  • 七、線程同步之條件變量

一、再論進程

1、進程的掛起、阻塞和睡眠的區(qū)別:

詳解:https://www.cnblogs.com/ck1020/p/6669661.html

(1)阻塞是一種被動的方式,由于獲取資源獲取不到而引起的等待。

(2)睡眠就是一種主動的方式,當一個進程獲取資源比如獲取最普通的鎖而失敗后,可以有兩種處理方式,1、自己睡眠,觸發(fā)調(diào)度;2、忙等待,使用完自己的時間。所以從這里看,睡眠的確是一種主動的方式,且僅僅作為一種處理手段。當然睡眠不僅僅用于阻塞,更多的,我們可以在適當?shù)臅r候設(shè)置讓進程睡眠一定的時間,那么在這里,就可以發(fā)現(xiàn),睡眠之前,我們已經(jīng)預先規(guī)定了,你只能睡多長時間,這段時間過后,比必須返回來工作。

??睡眠都是由用戶控制的,睡眠恢復則是自動完成的,睡眠時間到了則恢復到就緒態(tài),睡眠時線程不會釋放對象鎖。

(3)掛起是由用戶控制的,掛起恢復需要用戶主動控制,掛起時線程不會釋放對象鎖。

(4)舉個例子,形象解釋一下三者的關(guān)系(引用自:https://blog.csdn.net/qq784515681/article/details/79142127)
??掛起線程的意思就是你對主動對雇工說:“你睡覺去吧,用著你的時候我主動去叫你,然后接著干活”。

??使線程睡眠的意思就是你主動對雇工說:“你睡覺去吧,某時某刻過來報到,然后接著干活”。

??線程阻塞的意思就是,你突然發(fā)現(xiàn),你的雇工不知道在什么時候沒經(jīng)過你允許,自己睡覺呢,但是你不能怪雇工,肯定你這個雇主沒注意,本來你讓雇工掃地,結(jié)果掃帚被偷了或被鄰居家借去了,你又沒讓雇工繼續(xù)干別的活,他就只好睡覺了。至于掃帚回來后,雇工會不會知道,會不會繼續(xù)干活,你不用擔心,雇工一旦發(fā)現(xiàn)掃帚回來了,他就會自己去干活的。因為雇工受過良好的培訓。這個培訓機構(gòu)就是操作系統(tǒng)。

2、多進程實現(xiàn)同時讀取鍵盤和鼠標

https://blog.csdn.net/u011508527/article/details/46878205
https://blog.csdn.net/jobbofhe/article/details/82192092

思路:創(chuàng)建子進程,然后父子進程分別進行讀鍵盤和鼠標的工作

#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)建子進程,然后父子進程中分別進行讀鍵盤和鼠標的工作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){//子進程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("讀取的鼠標的內(nèi)容是:[%s].\n", buf);}}else if (ret > 0){//父進程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)中,是指一個時間段中有幾個程序都處于已啟動運行到運行完畢之間,且這幾個程序都是在同一個處理機上運行,但任一個時刻點上只有一個程序在處理機上運行。

操作系統(tǒng)并發(fā)程序執(zhí)行的特點,并發(fā)環(huán)境下,由于程序的封閉性被打破,出現(xiàn)了新的特點:
①程序與計算不再一一對應,一個程序副本可以有多個計算
②并發(fā)程序之間有相互制約關(guān)系,直接制約體現(xiàn)為一個程序需要另一個程序的計算結(jié)果,間接制約體現(xiàn)為多個程序競爭某一資源,如處理機、緩沖區(qū)等。
③并發(fā)程序在執(zhí)行中是走走停停,斷續(xù)推進的。

3、使用進程技術(shù)的優(yōu)勢
(1)CPU時分復用,單核心CPU可以實現(xiàn)宏觀上的并行

(2)實現(xiàn)多任務(wù)系統(tǒng)需求(多任務(wù)的需求是客觀的)

4、進程技術(shù)的劣勢
(1)進程間切換開銷大,進程的切換包含了進程斷點的保存與恢復,有些類似于中斷及中斷返回

(2)進程間通信麻煩而且效率低

5、解決方案就是線程技術(shù)
(1)線程技術(shù)保留了進程技術(shù)實現(xiàn)多任務(wù)的特性。
(2)線程的改進就是在線程間切換和線程間通信上提升了效率。
(3)多線程在多核心CPU上面更有優(yōu)勢。

二、線程的引入

詳解:https://www.cnblogs.com/yuanchenqi/articles/6755717.html

1、線程進程的區(qū)別體現(xiàn)在幾個方面

??因為進程擁有獨立的堆棧空間和數(shù)據(jù)段,所以每當啟動一個新的進程必須分配給它獨立的地址空間,建立眾多的數(shù)據(jù)表來維護它的代碼段、堆棧段和數(shù)據(jù)段,這對于多進程來說十分“奢侈”,系統(tǒng)開銷比較大。

??而線程不一樣,線程擁有獨立的堆棧空間,但是共享數(shù)據(jù)段,它們彼此之間使用相同的地址空間,共享大部分數(shù)據(jù),比進程更節(jié)儉,開銷比較小,切換速度也比進程快,效率高,但是正由于進程之間獨立的特點,使得進程安全性比較高,也因為進程有獨立的地址空間,一個進程崩潰后,在保護模式下不會對其它進程產(chǎn)生影響,而線程只是一個進程中的不同執(zhí)行路徑。一個線程死掉就等于整個進程死掉。(這句話可能有點問題,應該在某種條件下是對的,比如單線程的進程)

??當一個線程死了(非正常退出、死循環(huán)等)就會導致線程該占有的資源永遠無法釋放,從而影響其他線程的正常工作

(1)體現(xiàn)在通信機制上面,正因為進程之間互不干擾,相互獨立,進程的通信機制相對很復雜,譬如管道,信號,消息隊列,共享內(nèi)存,套接字等通信機制,而線程由于共享數(shù)據(jù)段所以通信機制很方便。

(2)屬于同一個進程的所有線程共享該進程的所有資源,包括文件描述符。而不同的進程相互獨立。

(3)線程又稱為輕量級進程,進程有進程控制塊,線程有線程控制塊;

(4)線程必定也只能屬于一個進程,而進程可以擁有多個線程而且至少擁有一個線程;

(5)體現(xiàn)在程序結(jié)構(gòu)上,舉一個簡明易懂的列子:當我們使用進程的時候,我們不自主的使用if else嵌套來判斷pid,使得程序結(jié)構(gòu)繁瑣,但是當我們使用線程的時候,基本上可以甩掉它,當然程序內(nèi)部執(zhí)行功能單元需要使用的時候還是要使用,所以線程對程序結(jié)構(gòu)的改善有很大幫助。

2、進程與線程的選擇取決以下幾點

(1)需要頻繁創(chuàng)建銷毀的優(yōu)先使用線程;因為對進程來說創(chuàng)建和銷毀一個進程代價是很大的。

(2)線程的切換速度快,所以在需要大量計算,切換頻繁時用線程,還有耗時的操作使用線程可提高應用程序的響應

(3)因為對CPU系統(tǒng)的效率使用上線程更占優(yōu),所以可能要發(fā)展到多機分布的用進程,多核分布用線程;

(4)并行操作時使用線程,如C/S 的服務(wù)器端并發(fā)線程響應用戶的請求;

(5)需要更穩(wěn)定安全時,適合選擇進程;需要速度時,選擇線程更好。

3、使用線程技術(shù)同時讀取鍵盤和鼠標

#include <pthread.h> int pthread_create(pthread_t *restrict tidp, //新創(chuàng)建的線程ID指向的內(nèi)存單元。const pthread_attr_t *restrict attr, //線程屬性,默認為NULLvoid *(*start_rtn)(void *), //新創(chuàng)建的線程從start_rtn函數(shù)的地址開始運行void *restrict arg //默認為NULL。若上述函數(shù)需要參數(shù),將參數(shù)放入結(jié)構(gòu)體中并將地址作為arg傳入。);restrict,C語言中的一種類型限定符,用于告訴編譯器,對象已經(jīng)被指針所引用,不能通過除該指針外所有其他直接或間接的方式修改該對象的內(nèi)容。pthread_create是(Unix、Linux、Mac OS X)等操作系統(tǒng)的創(chuàng)建線程的函數(shù)。它的功能是創(chuàng)建線程(實際上就是確定調(diào)用該線程函數(shù)的入口點),在線程創(chuàng)建以后,就開始運行相關(guān)的線程函數(shù)。pthread_create的返回值表示成功,返回0;表示出錯,返回表示-1。編譯時需加上 -lpthread選項,使其能找到相應的動態(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("讀取的鼠標的內(nèi)容是:[%s].\n", buf);}return 0; }

4、linux中的線程簡介

(1)一種輕量級進程

(2)線程是參與內(nèi)核調(diào)度的最小單元,操作系統(tǒng)工作時調(diào)度的單位

(3)一個進程中可以有多個線程,線程依附于進程而存在。進程一死,線程就全都結(jié)束了。一個進程中多個線程,某個線程死了,其他線程仍可存活。

5、線程技術(shù)的優(yōu)勢
https://www.cnblogs.com/valjeanshaw/p/11469514.html
(1)像進程一樣可被OS調(diào)度

(2)同一進程的多個線程之間很容易高效率通信,進程中的線程類似于一個程序中的不同函數(shù),進程類似于兩個程序。

(3)在多核心CPU(對稱多處理器架構(gòu)SMP)架構(gòu)下效率最大化

??操作系統(tǒng)的實現(xiàn)方法無法保證多個進程在多個CPU核心上運行,而多線程卻可以,可以保證,例如兩個線程在兩個核心上運行,當線程數(shù)超過CPU核心數(shù)則又會出現(xiàn)分時復用。

??核心數(shù)多的電腦不一定比核心數(shù)少的電腦運行的快,如果分別為4/8核電腦,而線程只有四個,則二者速度相同,若是8個線程,則8核心電腦的運行速度快。

6、并行處理是計算機系統(tǒng)中能同時執(zhí)行兩個或更多個處理的一種計算方法。并行處理可同時工作于同一程序的不同方面。并行處理的主要目的是節(jié)省大型和復雜問題的解決時間。

??并發(fā)處理:指一個時間段中有幾個程序都處于已啟動運行到運行完畢之間,且這幾個程序都是在同一個處理機(CPU)上運行,但任一個時刻點上只有一個程序在處理機(CPU)上運行。

??并發(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)用線程,直到指定的線程終止。當pthread_join()返回之后,應用程序可回收與已終止線程關(guān)聯(lián)的任何數(shù)據(jù)存儲空間。但是,同時需要注意,一定要和上面創(chuàng)建的某一線程配套使用,這樣還可以起到互斥的作用。否則多線程可能搶占CPU資源,導致運行結(jié)果不確定。

(3)pthread_detach 主線程用來分離子線程,分離后主線程不必再去回收子線程,子線程返回系統(tǒng)時自動回收。

??在默認情況下通過pthread_create函數(shù)創(chuàng)建的線程是非分離屬性的,由pthread_create函數(shù)的第二個參數(shù)決定,在非分離的情況下,當一個線程結(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ù)運行,直到運行到取消點的時候該線程才會退出

(2)pthread_setcancelstate 子線程設(shè)置自己是否允許被取消

#include <pthread.h> int pthread_setcancelstate(int state, int *oldstate);參數(shù):state:將當前狀態(tài)改為stateoldstate:將該線程原先的狀態(tài)放到oldtype所指向的空間里面返回值:成功返回0,失敗返回錯誤碼

??這兩步是一個原子操作。

??對于pthread_cancel函數(shù)默認的是PTHREAD_CANCEL_ENABLE可取消狀態(tài)。當狀態(tài)設(shè)為PTHREAD_CANCEL_DISABLE時,對于pthread_cancel的調(diào)用并不會殺死線程。相反,該取消請求對于這個線程還處于掛起狀態(tài)(也就是未決),直到線程的取消狀態(tài)變?yōu)镻THREAD_CANCEL_ENABLE時,線程將在下一個取消點(取消點是線程檢查他是否被取消的一個位置)上對所有的掛起請求進行處理。

(3)pthread_setcanceltype 設(shè)置退出的類型,只有(2)設(shè)置為允許,(3)才有效

#include <pthread.h> int pthread_setcanceltype(int type, int *oldtype);參數(shù):type:將取消的類型設(shè)置為typeoldtype:將該線程的取消類型放到oldtype所指向的空間里面 返回值:成功返回0,失敗返回錯誤碼

??我們默認的取消類型是推遲取消。也就是會運行到取消點再取消。對于可以設(shè)置的取消類型有PTHREAD_CANCEL_DEFERRED(推遲取消),也就是默認的取消類型。PTHRAED_CANCEL_ASYNCHRONOUS(異步取消),采用異步取消之后,線程可以在任意時間取消,不是非得到取消點才可以取消。

3、線程函數(shù)退出相關(guān)

詳解:https://blog.csdn.net/longbei9029/article/details/72871714

(1)pthread_exit與return退出,普通情況一樣。某些情況下不同,不可使用exit()函數(shù)退出,該函數(shù)會結(jié)束進程。導致其他線程也收到影響。

(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ù),這與進程可以用atexit函數(shù)安排進程退出時需要調(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);//子線程操作//子線程有可能在這里被主線程取消,但未來的及把鎖打開,導致其他線程無法進入//所以要想辦法在子線程死后,鎖不會繼續(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表示一個標準:可移植操作系統(tǒng)接口(英語:Portable Operating System Interface,縮寫為POSIX),而定義API的一系列互相關(guān)聯(lián)的標準的總稱,其正式稱呼為IEEE Std 1003,而是IEEE為要在各種UNIX操作系統(tǒng)上運行軟件國際標準名稱為ISO/IEC 9945。

四、線程同步之信號量

1、任務(wù):用戶從終端輸入任意字符然后統(tǒng)計個數(shù)顯示,輸入end則結(jié)束

??使用多線程實現(xiàn):主線程獲取用戶輸入并判斷是否退出,子線程計數(shù)

(1)為什么需要多線程實現(xiàn)

(2)問題和困難點是?

(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)容保持一致,或者按需要部分保持一致;文件同步,是指讓兩個或多個文件夾里的文件保持一致。而編程中、通信中所說的同步與生活中大家印象中的同步概念略有差異?!巴弊謶侵竻f(xié)同、協(xié)助、互相配合。主旨在協(xié)同步調(diào),按預定的先后次序運行。

3、線程同步
??同步即協(xié)同步調(diào),按預定的先后次序運行。

??線程同步,指一個線程發(fā)出某一功能調(diào)用時,在沒有得到結(jié)果之前,該調(diào)用不返回。同時其它線程為保證數(shù)據(jù)一致性,不能調(diào)用該功能。

舉例1: 銀行存款 5000。柜臺,折:取3000;提款機,卡:取 3000。剩余:2000

舉例2:內(nèi)存中100字節(jié),線程T1欲填入全1,線程T2欲填入全0。但如果T1執(zhí)行了50個字節(jié)失去cpu,T2執(zhí)行,會將T1寫過的內(nèi)容覆蓋。當T1再次獲得cpu繼續(xù)從失去cpu的位置向后寫入1,當執(zhí)行結(jié)束,內(nèi)存中的100字節(jié),既不是全1,也不是全0。

例1、2產(chǎn)生的現(xiàn)象叫做“與時間有關(guān)的錯誤”。為了避免這種數(shù)據(jù)混亂,線程需要同步?!巴健钡哪康?#xff0c;是為了避免數(shù)據(jù)混亂,解決與時間有關(guān)的錯誤。實際上,不僅線程間需要同步,進程間、信號間等等都需要同步機制。因此,所有“多個控制流,共同操作一個共享資源”的情況,都需要同步。

4、數(shù)據(jù)混亂原因
1)資源共享(獨享資源則不會)
2)調(diào)度隨機(意味著數(shù)據(jù)訪問會出現(xiàn)競爭)
3)線程間缺乏必要的同步機制。

??以上3點中,前兩點不能改變,欲提高效率,傳遞數(shù)據(jù),資源必須共享。只要共享資源,就一定會出現(xiàn)競爭。只要存在競爭關(guān)系,數(shù)據(jù)就很容易出現(xiàn)混亂。所以只能從第三點著手解決。使多個線程在訪問共享資源的時候,出現(xiàn)互斥。

5、信號量的介紹和使用
(1)信號:(signal)是一種處理異步事件的方式。信號是比較復雜的通信方式,用于通知接受進程有某種事件發(fā)生,除了用于進程外,還可以發(fā)送信號給進程本身。

(2)信號量:(Semaphore)進程間通信處理同步互斥的機制。是在多線程環(huán)境下使用的一種設(shè)施,它負責協(xié)調(diào)各個線程, 以保證它們能夠正確、合理的使用公共資源。

??信號量是一個特殊類型的變量,它可以被增加或減少,但對其的關(guān)鍵訪問被保證是原子操作,即使在一個多線程程序中也是如此。這意味著如果一個程序中有兩個(或更多)的線程試圖改變一個信號量的值,系統(tǒng)將保證所有的操作都將依次進行。但如果是普通變量,來自同一程序中不同線程的沖突操作所導致的結(jié)果將是不確定的。

??最簡單的信號量是二進制信號量,它只有0和1兩種取值。還有更通用的信號量——計數(shù)信號量,它可以有更大的取值范圍。信號量一般常用來保護一段代碼,使其每次只能被一個執(zhí)行線程運行,要完成這個工作,就要使用二進制信號量。有時,我們希望可以允許有限數(shù)目的線程執(zhí)行一段指定的代碼,這就需要計數(shù)信號量。

(3)信號量和信號的區(qū)別
??簡單地說,信號就是一種異步通信,通知進程某種事件的發(fā)生;信號量是進程/線程同步與互斥的一種機制,保證進程/線程間之間的有序執(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ù)指明信號量是由進程內(nèi) 線程共享,還是由進程之間共享。如果pshared的值初始化一個定位在sem的匿名信號量為0, 那么信號量將被進程內(nèi)的線程共享,并且應該放置在所有線程都可見的地址上(如全局變量, 或者堆上動態(tài)分配的變量)。如果pshared是非零值,那么信號量將在進程之間共享,并且應該定位共享內(nèi)存區(qū)域 (shm_open(3)mmap(2)shmget(2)).(因為通過fork(2)創(chuàng)建的孩子繼承其父親的 內(nèi)存映射,因此它也可以見到這個信號量。)所有可以訪問共享內(nèi)存區(qū)域的進程都可以使用sem_post(3)、sem_wait(3) 等等操作信號量。初始化一個已經(jīng)初始的信號量其結(jié)果未定義。返回值:sem_init() 成功時返回 0;錯誤時,返回 -1,并把 errno 設(shè)置為合適的值。錯誤:EINVALvalue 超過 SEM_VALUE_MAX。ENOSYSpshared 非零,但系統(tǒng)還沒有支持進程共享的信號量。int sem_trywait(sem_t *sem);//sem_wait的非阻塞版本sem_trywait函數(shù)是sem_wait的非阻塞版本,sem_trywait()函數(shù)僅在信號量當前沒有鎖定 的情況下(也就是說,如果信號量值是正的),鎖定由sem所引用的信號量;否則,它就不能鎖定 信號量。如果調(diào)用進程成功地執(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”,但它永遠會先 等待該信號量為一個非零值才開始做減法。也就是說,如果你對一個值為2的信號量調(diào) 用sem_wait(),線程將會繼續(xù)執(zhí)行,這信號量的值將減到1。如果對一個值為0的信號量調(diào)用 sem_wait(),這個函數(shù)就會地等待直到有其它線程增加了這個值使它不再是0為止。如果有兩 個線程都在sem_wait()中等待同一個信號量變成非零值,那么當它被第三個線程增加一個 “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”操作的.兩個線程是不會沖突的;而同時對同一個文件進行讀、加和寫操作的兩個程序就有可能會引起 沖突。信號量的值永遠會正確地加一個“2”--因為有兩個線程試圖改變它。函數(shù)sem_post()用來增加信號量的值。當有線程阻塞在這個信號量上時,調(diào)用這個函數(shù)會使其 中的一個線程不在阻塞,選擇機制同樣是由線程的調(diào)度策略決定的。函數(shù)sem_wait()被用來阻 塞當前線程直到信號量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) {//子線程首先應該有個循環(huán)//循環(huán)中阻塞在等待主線程激活的時候,子線程被激活后就去獲取buf中的字符//長度,然后打印,完成后再次被阻塞sem_wait(&sem);//等待信號量,如果信號量的值大于0,將信號量的值減1,//立即返回。如果信號量的值為0,則線程阻塞。相當于//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);}//主線程在收到用戶輸入的字符串,并且確認不是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)生了。但應注意:同一時刻,只能有一個線程持有該鎖。

??當A線程對某個全局變量加鎖訪問,B在訪問前嘗試加鎖,拿不到鎖,B阻塞。C線程不去加鎖,而直接訪問該全局變量,依然能夠訪問,但會出現(xiàn)數(shù)據(jù)混亂?;コ怄i實質(zhì)上是操作系統(tǒng)提供的一把“建議鎖”(又稱“協(xié)同鎖”),建議程序中有多線程訪問共享資源的時候使用該機制。但并沒有強制限定。

??因此,即使有了mutex,如果有線程不按規(guī)則來訪問數(shù)據(jù),依然會造成數(shù)據(jù)混亂.

(2)何謂自旋鎖?
??它是為實現(xiàn)保護共享資源而提出一種鎖機制。其實,自旋鎖與互斥鎖比較類似,它們都是為了解決對某項資源的互斥使用。

??無論是互斥鎖,還是自旋鎖,在任何時刻,最多只能有一個保持者,也就說,在任何時刻最多只能有一個執(zhí)行單元獲得鎖。但是兩者在調(diào)度機制上略有不同。

??對于互斥鎖,如果資源已經(jīng)被占用,資源申請者只能進入睡眠狀態(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

#include <pthread.h> int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr); int pthread_mutex_destroy(pthread_mutex_t *mutex); int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex);//以上5個函數(shù)的返回值都是:成功返回0, 失敗返回錯誤號。 //pthread_mutex_t 類型,其本質(zhì)是一個結(jié)構(gòu)體。為簡化理解,應用時可忽略其實現(xiàn)細節(jié),簡單當成整數(shù)看待。 //pthread_mutex_t mutex;變量mutex只有兩種取值1、0。

(4)互斥鎖和信號量的關(guān)系:可以認為互斥鎖是一種特殊的信號量,值只能是0和1的信號量

(5)互斥鎖主要用來實現(xiàn)關(guān)鍵段保護

2、用互斥鎖來實現(xiàn)上小節(jié)的代碼

注意:man 3 pthread_mutex_init時提示找不到函數(shù),說明你沒有安裝pthread相關(guān)的man手冊。

安裝方法:1、虛擬機上網(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) {//子線程首先應該有個循環(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)。條件變量是利用線程間共享的全局變量進行同步的一種機制,主要包括兩個動作:一個線程等待"條件變量的條件成立"而掛起;另一個線程使"條件成立"(給出條件成立信號)。

??條件的檢測是在互斥鎖的保護下進行的。如果一個條件為假,一個線程自動阻塞,并釋放等待狀態(tài)改變的互斥鎖。如果另一個線程改變了條件,它發(fā)信號給關(guān)聯(lián)的條件變量,喚醒一個或多個等待它的線程,重新獲得互斥鎖,重新評價條件。如果兩進程共享可讀寫的內(nèi)存,條件變量可以被用來實現(xiàn)這兩進程間的線程同步。

??使用條件變量之前要先進行初始化。可以在單個語句中生成和初始化一個條件變量如:

pthread_cond_t my_condition=PTHREAD_COND_INITIALIZER; //(用于進程間線程的通信)。可以利用函數(shù)pthread_cond_init動態(tài)初始化。

??條件變量分為兩部分: 條件和變量.。條件本身是由互斥量保護的,線程在改變條件狀態(tài)前先要鎖住互斥量。它利用線程間共享的全局變量進行同步的一種機制。

??互斥鎖是用來給資源上鎖的,而條件變量是用來等待而不是用來上鎖的。條件變量用來自動阻塞一個線程,直到某特殊情況發(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, 在使用之前必須要進行初始化, 這包括兩種方式:

靜態(tài): 可以把常量PTHREAD_COND_INITIALIZER給靜態(tài)分配的條件變量.
動態(tài): pthread_cond_init函數(shù), 釋放動態(tài)條件變量的內(nèi)存空間之前, 要用pthread_cond_destroy對其進行清理.

#include <pthread.h>int pthread_cond_init(pthread_cond_t *restrict cond, pthread_condattr_t *restrict attr); int pthread_cond_destroy(pthread_cond_t *cond);成功則返回0, 出錯則返回錯誤編號. 當pthread_cond_init的attr參數(shù)為NULL, 會創(chuàng)建一個默認屬性的條件變量; 非默認情況以后討論.

(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="ozvdkddzhkzd" class="token punctuation">, 傳遞給pthread_cond_wait的互斥量對條件進行保護, 調(diào)用 者把鎖住的互斥量傳遞給函數(shù). 函數(shù)把調(diào)用線程放到等待條件的線程列表上, 然后對互斥量 解鎖, 這兩個操作是原子的. 這樣便關(guān)閉了條件檢查和線程進入休眠狀態(tài)等待條件改變這兩 個操作之間的時間通道, 這樣線程就不會錯過條件的任何變化.當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) {//子線程首先應該有個循環(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ū)別:

(1)互斥鎖必須總是由給它上鎖的線程解鎖,信號量的掛出不必由執(zhí)行過它的等待操作的同一進程執(zhí)行。一個線程可以等待某個給定信號燈,而另一個線程可以掛出該信號燈。(2)互斥鎖要么鎖住,要么被解開(二值狀態(tài),類型二值信號量)。(3)由于信號量有一個與之關(guān)聯(lián)的狀態(tài)(它的計數(shù)值),信號量掛出操作總是被記住。然而當向一個條件變量發(fā)送信號時,如果沒有線程等待在該條件變量上,那么該信號將丟失。(4)互斥鎖是為了上鎖而設(shè)計的,條件變量是為了等待而設(shè)計的,信號變量既可用于上鎖,也可用于等待,因而可能導致更多的開銷和更高的復雜性。

(2)為什么有了互斥鎖還要有條件變量?
?? 詳解:https://blog.csdn.net/weixin_43812622/article/details/97389712

注:本資料大部分由朱老師物聯(lián)網(wǎng)大講堂課程筆記整理而來并且引用了部分他人博客的內(nèi)容,如有侵權(quán),聯(lián)系刪除!水平有限,如有錯誤,歡迎各位在評論區(qū)交流。

總結(jié)

以上是生活随笔為你收集整理的linux线程全解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。