Linux多線程與同步

https://www.cnblogs.com/freedomabcd/p/7774743.html

典型的UNIX系統都支持一個進程創建多個線程(thread)。在Linux進程基礎中提到,Linux以進程為單位組織操作,Linux中的線程也都基于進程。盡管實現方式有異于其它的UNIX系統,但Linux的多線程在邏輯和使用上與真正的多線程并沒有差別。

?

多線程

我們先來看一下什么是多線程。在Linux從程序到進程中,我們看到了一個程序在內存中的表示。這個程序的整個運行過程中,只有一個控制權的存在。當函數被調用的時候,該函數獲得控制權,成為激活(active)函數,然后運行該函數中的指令。與此同時,其它的函數處于離場狀態,并不運行。如下圖所示:

Linux從程序到進程

?

我們看到,各個方塊之間由箭頭連接。各個函數就像是連在一根線上一樣,計算機像一條流水線一樣執行各個函數中定義的操作。這樣的一個程序叫做單線程程序。


多線程就是允許一個進程內存在多個控制權,以便讓多個函數同時處于激活狀態,從而讓多個函數的操作同時運行。即使是單CPU的計算機,也可以通過不停地在不同線程的指令間切換,從而造成多線程同時運行的效果。如下圖所示,就是一個多線程的流程:

main()到func3()再到main()構成一個線程,此外func1()和func2()構成另外兩個線程。操作系統一般都有一些系統調用來讓你將一個函數運行成為一個新的線程。

?

回憶我們在Linux從程序到進程中提到的棧的功能和用途。一個棧,只有最下方的幀可被讀寫。相應的,也只有該幀對應的那個函數被激活,處于工作狀態。為了實現多線程,我們必須繞開棧的限制。為此,創建一個新的線程時,我們為這個線程建一個新的棧。每個棧對應一個線程。當某個棧執行到全部彈出時,對應線程完成任務,并收工。所以,多線程的進程在內存中有多個棧。多個棧之間以一定的空白區域隔開,以備棧的增長。每個線程可調用自己棧最下方的幀中的參數和變量,并與其它線程共享內存中的Text,heap和global data區域。對應上面的例子,我們的進程空間中需要有3個棧。

(要注意的是,對于多線程來說,由于同一個進程空間中存在多個棧,任何一個空白區域被填滿都會導致stack overflow的問題。)

?

并發

多線程相當于一個并發(concunrrency)系統。并發系統一般同時執行多個任務。如果多個任務可以共享資源,特別是同時寫入某個變量的時候,就需要解決同步的問題。比如說,我們有一個多線程火車售票系統,用全局變量i存儲剩余的票數。多個線程不斷地賣票(i = i - 1),直到剩余票數為0。所以每個都需要執行如下操作:

復制代碼
復制代碼
/*mu is a global mutex*/

while (1) { ?? /*infinite loop*/if (i != 0) i = i -1else {printf("no more tickets");exit();} }
復制代碼
復制代碼

如果只有一個線程執行上面的程序的時候(相當于一個窗口售票),則沒有問題。但如果多個線程都執行上面的程序(相當于多個窗口售票), 我們就會出現問題。我們會看到,其根本原因在于同時發生的各個線程都可以對i讀取和寫入。

我們這里的if結構會給CPU兩個指令, 一個是判斷是否有剩余的票(i != 0), 一個是賣票 (i = i -1)。某個線程會先判斷是否有票(比如說此時i為1),但兩個指令之間存在一個時間窗口,其它線程可能在此時間窗口內執行賣票操作(i = i -1),導致該線程賣票的條件不再成立。但該線程由于已經執行過了判斷指令,所以無從知道i發生了變化,所以繼續執行賣票指令,以至于賣出不存在的票 (i成為負數)。對于一個真實的售票系統來說,這將成為一個嚴重的錯誤 (售出了過多的票,火車爆滿)。

在并發情況下,指令執行的先后順序由內核決定。同一個線程內部,指令按照先后順序執行,但不同線程之間的指令很難說清除哪一個會先執行。如果運行的結果依賴于不同線程執行的先后的話,那么就會造成競爭條件(race condition),在這樣的狀況下,計算機的結果很難預知。我們應該盡量避免競爭條件的形成。最常見的解決競爭條件的方法是將原先分離的兩個指令構成不可分隔的一個原子操作(atomic operation),而其它任務不能插入到原子操作中。

?

多線程同步

對于多線程程序來說,同步(synchronization)是指在一定的時間內只允許某一個線程訪問某個資源 。而在此時間內,不允許其它的線程訪問該資源。我們可以通過互斥鎖(mutex),條件變量(condition variable)和讀寫鎖(reader-writer lock)來同步資源。

?

1) 互斥鎖

互斥鎖是一個特殊的變量,它有鎖上(lock)和打開(unlock)兩個狀態。互斥鎖一般被設置成全局變量。打開的互斥鎖可以由某個線程獲得。一旦獲得,這個互斥鎖會鎖上,此后只有該線程有權打開。其它想要獲得互斥鎖的線程,會等待直到互斥鎖再次打開的時候。我們可以將互斥鎖想像成為一個只能容納一個人的洗手間,當某個人進入洗手間的時候,可以從里面將洗手間鎖上。其它人只能在互斥鎖外面等待那個人出來,才能進去。在外面等候的人并沒有排隊,誰先看到洗手間空了,就可以首先沖進去。

上面的問題很容易使用互斥鎖的問題解決,每個線程的程序可以改為:

復制代碼
復制代碼
/*mu is a global mutex*/

while (1) { /*infinite loop*/mutex_lock(mu); ? ? ? /*aquire mutex and lock it, if cannot, wait until mutex is unblocked*/if (i != 0) i = i - 1;else {printf("no more tickets");exit();}mutex_unlock(mu); ? ? /*release mutex, make it unblocked*/ }
復制代碼
復制代碼

第一個執行mutex_lock()的線程會先獲得mu。其它想要獲得mu的線程必須等待,直到第一個線程執行到mutex_unlock()釋放mu,才可以獲得mu,并繼續執行線程。所以線程在mutex_lock()和mutex_unlock()之間的操作時,不會被其它線程影響,就構成了一個原子操作。

需要注意的時候,如果存在某個線程依然使用原先的程序 (即不嘗試獲得mu,而直接修改i),互斥鎖不能阻止該程序修改i,互斥鎖就失去了保護資源的意義。所以,互斥鎖機制需要程序員自己來寫出完善的程序來實現互斥鎖的功能。我們下面講的其它機制也是如此。

?

2) 條件變量

條件變量是另一種常用的變量。它也常常被保存為全局變量,并和互斥鎖合作。

?

假設這樣一個狀況: 有100個工人,每人負責裝修一個房間。當有10個房間裝修完成的時候,老板就通知相應的十個工人一起去喝啤酒。

我們如何實現呢?老板讓工人在裝修好房間之后,去檢查已經裝修好的房間數。但多線程條件下,會有競爭條件的危險。也就是說,其他工人有可能會在該工人裝修好房子和檢查之間完成工作。采用下面方式解決:

復制代碼
復制代碼
/*mu: global mutex, cond: global codition variable, num: global int*/
mutex_lock(mu)num = num + 1; /*worker build the room*/if (num <= 10) { /*worker is within the first 10 to finish*/cond_wait(mu, cond); ? ? /*wait*/printf("drink beer"); } else if (num = 11) { /*workder is the 11th to finish*/cond_broadcast(mu, cond); ? ? ? ?/*inform the other 9 to wake up*/ }mutex_unlock(mu);
復制代碼
復制代碼

上面使用了條件變量。條件變量除了要和互斥鎖配合之外,還需要和另一個全局變量配合(這里的num, 也就是裝修好的房間數)。這個全局變量用來構成各個條件。

?

具體思路如下。我們讓工人在裝修好房間(num = num + 1)之后,去檢查已經裝修好的房間數( num < 10 )。由于mu被鎖上,所以不會有其他工人在此期間裝修房間(改變num的值)。如果該工人是前十個完成的人,那么我們就調用cond_wait()函數。
cond_wait()做兩件事情,一個是釋放mu,從而讓別的工人可以建房。另一個是等待,直到cond的通知。這樣的話,符合條件的線程就開始等待。

當有通知(第十個房間已經修建好)到達的時候,condwait()會再次鎖上mu。線程的恢復運行,執行下一句prinft("drink beer")?(喝啤酒!)。從這里開始,直到mutex_unlock(),就構成了另一個互斥鎖結構。

那么,前面十個調用cond_wait()的線程如何得到的通知呢?我們注意到elif if,即修建好第11個房間的人,負責調用cond_broadcast()。這個函數會給所有調用cond_wait()的線程放送通知,以便讓那些線程恢復運行。

?

條件變量特別適用于多個線程等待某個條件的發生。如果不使用條件變量,那么每個線程就需要不斷嘗試獲得互斥鎖并檢查條件是否發生,這樣大大浪費了系統的資源。

?

3) 讀寫鎖

讀寫鎖與互斥鎖非常相似。r、RW lock有三種狀態:?共享讀取鎖(shared-read),?互斥寫入鎖(exclusive-write lock),?打開(unlock)。后兩種狀態與之前的互斥鎖兩種狀態完全相同。

一個unlock的RW lock可以被某個線程獲取R鎖或者W鎖。

如果被一個線程獲得R鎖,RW lock可以被其它線程繼續獲得R鎖,而不必等待該線程釋放R鎖。但是,如果此時有其它線程想要獲得W鎖,它必須等到所有持有共享讀取鎖的線程釋放掉各自的R鎖。

如果一個鎖被一個線程獲得W鎖,那么其它線程,無論是想要獲取R鎖還是W鎖,都必須等待該線程釋放W鎖。

這樣,多個線程就可以同時讀取共享資源。而具有危險性的寫入操作則得到了互斥鎖的保護。

?

我們需要同步并發系統,這為程序員編程帶來了難度。但是多線程系統可以很好的解決許多IO瓶頸的問題。比如我們監聽網絡端口。如果我們只有一個線程,那么我們必須監聽,接收請求,處理,回復,再監聽。如果我們使用多線程系統,則可以讓多個線程監聽。當我們的某個線程進行處理的時候,我們還可以有其他的線程繼續監聽,這樣,就大大提高了系統的利用率。在數據越來越大,服務器讀寫操作越來越多的今天,這具有相當的意義。多線程還可以更有效地利用多CPU的環境。

(就像做飯一樣,不斷切換去處理不同的菜。)

?

本文中的程序采用偽C的寫法。不同的語言有不同的函數名(比如mutex_lock)。這里關注的是邏輯上的概念,而不是具體的實現和語言規范。

?

總結

multiple threads, multiple stacks

race condition

mutex, condition variable, RW lock


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/383998.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/383998.shtml
英文地址,請注明出處:http://en.pswp.cn/news/383998.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

內存管理(二)

頁面置換算法 當發生缺頁中斷的時候, 系統會在內存中選擇一個頁面將其換出內存, 而當換出內存的時候如果該頁面的內容在內存中發生修改,則必須將該新數據重新寫回到磁盤, 然后再將需要換進的數據覆蓋掉原來的數據, 而當該數據在內存中沒有被修改的時候, 此時就直接用需要換進的…

兩個棧實現一個隊列/兩個隊列實現一個棧

http://blog.csdn.net/sinat_30472685/article/details/70157227 1兩個棧實現一個隊列 1.原理分析&#xff1a; 隊列的主要操作有兩個&#xff1a;入隊操作和出隊操作&#xff0c;出隊時從隊頭出&#xff0c;入隊是從隊尾插入&#xff0c;入隊的操作和入棧的操作類似&#xff0…

UVa1588

【題目描述】 傳送門 【題目分析】 剛開始想了一會沒有想到什么很好的算法&#xff0c;看到了長度最多為100&#xff0c;就知道自己想的沒有什么意義了&#xff0c;直接暴力&#xff0c;把每一種填法都試一下就知道了。適當剪枝一下&#xff08;一個簡單的樂觀函數&#xff…

轉:C++中const、volatile、mutable的用法

const修飾普通變量和指針 const修飾變量&#xff0c;一般有兩種寫法&#xff1a; const TYPE value; TYPE const value; 這兩種寫法在本質上是一樣的。它的含義是&#xff1a;const修飾的類型為TYPE的變量value是不可變的。對于一個非指針的類型TYPE&#xff0c;無論怎么寫&…

數據鏈路

廣播信道的數據鏈路層 局域網的優點 網絡為一個單位所擁有, 地理范圍和站點數有限 局域網具有廣播特性, 可以從一個站點方便地訪問到整個網絡. 各個主機之間可以共享資源, 無論是局域網上的硬件資源還是局域網上的軟件資源 便于系統的擴展換和演化, 各個設備之間的位置可靈…

UVa11809

【題目描述】 傳送門 【題目分析】 終于把這道題做完了&#xff0c;之前一直連題意都看不懂。實在不行上網找了一下大佬的博客&#xff0c;看懂題意后自己寫&#xff0c;發現讀入很難處理&#xff0c;就又學習了一下大佬的讀入方法&#xff0c;用的是C里面的sstream&#xf…

數據鏈路層:基本概念

數據鏈路層的定義 對數據鏈路層有對上的網絡層接口. 對下提供物理層的接口. 定義合適的傳輸差錯率 對傳輸流進行管理, 以免快速的傳輸的數據被淹沒. 比如發送端發送信號太快, 接受方接受速度較慢, 此時數據鏈路層就需要提供一定的功能解決這個問題 物理層上傳輸的基本單元是…

C++的沉迷與愛戀

每年的 09/28 於我都是一個特殊的日子 -- 不只是因為教師節。今年很特殊地沒有普天同慶&#xff0c;那麼我就寫篇文章自己慶祝一下好了。我於今年七月發表了一本著作《多型與虛擬》和一本譯作《深度探索C物件模型》&#xff0c;獲得很大的回響。這些作品都不是針對 C 的完全初學…

Insertion Sort——打表找規律

【題目描述】 Insertion sort is a simple sorting algorithm that builds the final sorted array one item at an iteration.More precisely, insertion sort iterates, consuming one input element each repetition, and growing a sorted output list. At each iteration…

數據鏈路層: 可靠性傳輸 六個協議

可靠性傳輸 1. 差錯控制 發送方將數據幀發送, 但是當發送方發送的是一個 1的時候此時接受方卻接受的是一個 0. (1)校驗 接收方如果幀校驗接受到的幀沒有問題, 則對發送方發送一個肯定性的確認, 當對這個數據幀進行校驗發現這個幀有問題的時候, 此時接受方一種是將這個數據幀…

c語言實現配置文件的讀寫

配置文件的格式如下&#xff1a; key1 value1 key2 value2 . . . 名值對以一個鏈接&#xff0c;一條記錄以換行符分割 頭文件&#xff1a; #include<stdio.h> #include<stdlib.h> #include <string.h> 函數原型&#xff1a; void trim(char *strIn, char *…

Educational Codeforces Round 73 (Rated for Div. 2)

A 很簡單的一個模擬&#xff0c;只要前面的數字有兩個以上就能合成后面的&#xff0c;我們進行一遍合成看能不能出現2048就可以了。 #include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<iostream> #include&…

數據鏈路層: HDLC

一. 協議機 發送方和接收方. 同時有限狀態機把協議形式化為一個四元組 (S,M,I,T), 其中你S表示進程和信道可能進入的集合, M 表示數據幀的狀態, I 表示進程的初始狀態, T 表示兩兩狀態之間的轉化. 每個系統狀態可以分為發送狀態, 接受狀態和信道狀態. 把狀態用一個點進行表示,…

Miller_Rabin算法

為了測試一個大整數是不是素數&#xff0c;我們不能夠使用傳統的測試是否有因子的方法&#xff0c;因為那樣的時間復雜度至少也是O(n)O(n)O(n)&#xff0c;空間復雜度是O(n)O(n)O(n)&#xff08;使用線性篩數法&#xff09;&#xff0c;時間復雜度還好說&#xff0c;空間復雜度…

bob-tong 字符串函數之Strtok()函數

https://www.cnblogs.com/Bob-tong/p/6610806.html Strtok()函數詳解&#xff1a; 該函數包含在"string.h"頭文件中 函數原型&#xff1a; char* strtok (char* str,constchar* delimiters ); 函數功能&#xff1a; ??切割字符串&#xff0c;將str切分成一個個子…

數據鏈路層:SLIP(串型線路IP) PPP(點對點協議)

SLIP 沒有差錯控制, 傳輸時必須知道對方IP, 傳輸使用于低速業務 19.2k.應用非常受限 PPP協議 1. PPP協議功能 處理錯誤檢測 支持多協議(IP, IPX, DECnet 等) 連接時允許協商 IP 地址 允許身份驗證 2. PPP 的組成 串型鏈路上封裝數據報, 即支持異步鏈路也支持面向 比特…

Honeycomb——BFS

【題目描述】 傳送門 【題目分析】 看起來很復雜好像還要建圖什么的&#xff0c;其實直接在原圖上BFS就可以了&#xff0c;設置一下方向數組&#xff0c;然后直接跑就可以了。 【AC代碼】 #include<cstdio> #include<cstring> #include<algorithm> #inc…

C語言中strspn()函數和strcspn()函數的對比使用

C語言strspn()函數&#xff1a;計算字符串str中連續有幾個字符都屬于字符串accept 頭文件&#xff1a;#include <string.h> strspn() 函數用來計算字符串 str 中連續有幾個字符都屬于字符串 accept&#xff0c;其原型為&#xff1a; size_t strspn(const char *str, con…

Codeforces Round #587 (Div. 3)

A 只要每兩個都不一樣就可以&#xff0c;一旦出現兩個一樣的就改一個。 #include<cstdio> #include<cstring> #include<algorithm> #include<climits> #include<cctype> #include<queue> #include<set>using namespace std;typede…

信道分配 以太網

1.頻分復用 將信道分為多個頻帶, 用戶得到某個頻帶后,在通信的過程中, 自始至終都都占用這個信道.即頻分復用中, 所有用戶同時占用不同頻帶的信道 2. 時分信道 將時間劃分為一段一段的等長時分復用幀, 每個用戶在不同時間占用相同的數據幀 3. CSMA/CD 載波監聽多點接入/碰撞…