日本搞逼视频_黄色一级片免费在线观看_色99久久_性明星video另类hd_欧美77_综合在线视频

國內(nèi)最全IT社區(qū)平臺 聯(lián)系我們 | 收藏本站
阿里云優(yōu)惠2
您當前位置:首頁 > php框架 > 框架設(shè)計 > [CSAPP筆記][第十二章并發(fā)編程]

[CSAPP筆記][第十二章并發(fā)編程]

來源:程序員人生   發(fā)布時間:2016-06-12 08:56:52 閱讀次數(shù):3711次

第102章 并發(fā)編程

如果邏輯控制流在時間上是堆疊,那末它們就是并發(fā)的(concurrent)。這類常見的現(xiàn)象稱為并發(fā)(concurrency)

  • 硬件異常處理程序,進程和Unix信號處理程序都是大家熟習(xí)的例子。

我們主要將并發(fā)看作是1種操作系統(tǒng)內(nèi)核用來運行多個利用程序的機制。

  • 但是,并發(fā)不單單局限于內(nèi)核。它也能夠在利用程序中扮演重要的角色。

    • 例如

      • Unix信號處理程序如何允許利用響應(yīng)異步事件
        • 例如:用戶鍵入ctrl-c
        • 程序訪問虛擬存儲器的1個未定義的區(qū)域
    • 其他情況

      • 訪問慢速I/O裝備

        • 當1個利用程序正在等待來自慢速I/O裝備(例如磁盤)的數(shù)據(jù)到達時,內(nèi)核會運行其他進程,使CPU保持繁忙。
      • 與人交互

        • 和計算機交互的人要求計算機有同時履行多個任務(wù)的能力。
      • 通過推延工作以下降延遲

        • 有時,利用程序能夠通過推延其他操作和并發(fā)履行它們,利用并發(fā)來下降某些操作的延遲
      • 服務(wù)多個網(wǎng)絡(luò)客戶端
        • 1個慢速的客戶端可能會致使服務(wù)器謝絕為所有客戶端提供服務(wù)。
      • 在多核機器上進行并行運算

使用利用級并發(fā)的利用程序稱為并發(fā)程序(concurrent program).

  • 操作系統(tǒng)提供3種基本的構(gòu)造并發(fā)程序的方法:

    • 進程

      • 每一個邏輯控制流 都是1個進程

        • 由內(nèi)核來調(diào)度和保護。
      • 由于進程有獨立的虛擬地址空間

        • 和其他進程通訊,控制流必須使用某種顯式的進程間通訊(interprocess communication,IPC)進制
    • I/O多路復(fù)用(暫時不太懂)
      • 利用程序在1個進程的上下文中顯示地調(diào)度它們自己的邏輯流
      • 邏輯流被模型化為狀態(tài)機,數(shù)據(jù)到達文件描寫符后,主程序顯式地從1個狀態(tài)轉(zhuǎn)換到另外一個狀態(tài)。
      • 由于程序是1個單獨的進程,所以所有的流都同享同1個地址空間。
    • 線程
      • 線程是運行在1個單1進程上下文中的邏輯流,有內(nèi)核調(diào)度。
        • 進程1樣由內(nèi)核進行調(diào)度。
        • 而像I/O多路復(fù)用1流1樣同享1個虛擬地址空間。

12.1 基于進程的的并發(fā)編程

1個構(gòu)造并發(fā)服務(wù)器的自然方法就是,在父進程中接收客戶端連接要求,然后創(chuàng)建1個新的子進程來為每一個新客戶端提供服務(wù)。

  • 服務(wù)器正在監(jiān)聽1個監(jiān)聽描寫符(描寫符3)上的連接要求
  • 服務(wù)器接收客戶端1的連接要求
  • 并返回1個已連接描寫符(描寫符4)。

  • 子進程取得服務(wù)器描寫符表的完全拷貝(描寫符3,4)
  • 子進程關(guān)閉它的拷貝中的監(jiān)聽描寫符3
  • 服務(wù)器關(guān)閉描寫符表中的描寫符4

  • 以后新的客戶端又類似之前兩個步驟。

12.1.1 基于進程的并發(fā)服務(wù)器

  • Signal(SIGCHLD,sigchld_handler)回收僵死進程。

    • 具體細節(jié)見8.5.7
  • 28行,33行 父子進程各自關(guān)閉他們不需要的拷貝。

  • 由于文件表項的援用計數(shù),直到父進程關(guān)閉它的描寫符,才算結(jié)束1次連接

12.1.2 關(guān)于進程的優(yōu)劣

對在父,子進程間同享狀態(tài)信息,進程有1個非常清晰的模型。

  • 同享文件表,但是不同享用戶地址空間。
  • 進程具有獨立的虛擬地址空間即是 優(yōu)點,也是 缺點。

    • 優(yōu)點:1個進程不可能不謹慎覆蓋另外一個進程的虛擬存儲空間。

      • 消除許多使人迷惑的毛病。
    • 缺點:獨立的地址空間使得進程間同享信息也很困難。

      • 必須使用顯式的IPC(進程間通訊)機制。

      • 常常還比較

        • 進程控制IPC的開消都很大。

12.2 基于I/O多路復(fù)用的并發(fā)編程(暫時跳過)

假定要編寫1個echo服務(wù)器。

  • 服務(wù)器既能響應(yīng)客戶端的要求
  • 也能對用戶從標準輸入輸出的交互命令做出反應(yīng)(如exit).

因此,服務(wù)器必須要響應(yīng)兩個相互獨立的I/O事件

  • 網(wǎng)絡(luò)客戶端發(fā)起連接
  • 用戶在鍵盤鍵入命令行。

不管先等待那個事件都不是理想的,解決辦法之1是就是使用I/O多路復(fù)用技術(shù)。

  • 基本的思路
    • 使用select函數(shù),要求內(nèi)核掛起進程,只有1個或多個I/O事件產(chǎn)生后,才將控制返回給利用程序。

12.3 基于線程的并發(fā)編程

線程(thread) 就是運行在進程上下文中的邏輯流。

  • 線程由內(nèi)核調(diào)度。
  • 每一個線程都有它自己的線程上下文(thread context).

    • 包括1個唯1的整數(shù)線程ID(Thread ID,TID).
    • 棧和棧指針
    • 程序計數(shù)器
    • 通用目的寄存器和條件碼
  • 所有運行在該進程里的線程同享該進程的全部虛擬地址空間。

    • 同享 包括代碼,數(shù)據(jù),堆,同享庫和打開的文件。

12.3.1 線程履行模型

  • 每一個進程開始生命周期時都是單1線程,這個線程稱為主線程(main thread)。

    • 某時刻,主線程創(chuàng)建1個對等線程(peer thread)。
      • 當主線程履行1個慢速系統(tǒng)調(diào)用,例如readsleep
      • 或被系統(tǒng)的間隔計時器中斷。
      • 控制就會通過上下文切換傳遞到對等線程。
      • 對等線程履行1段時間,將控制傳遞回主線程。
  • 在某些方面,線程履行是不同等于進程的。

    • 線程的上下文切換的開消比進程的小很多,快很多
    • 線程不是依照嚴格的父子層次來組織。
      • 和1個進程相干的線程組成1個線程池(pool)。
        • 線程池概念的主要影響是
        • 1個線程可以殺死它的任何對等線程,或等待任意對等線程終止。
        • 每一個對等線程都能讀寫相同的同享數(shù)據(jù)。

12.3.2 Posix 線程

Posix線程 (Pthreads)是在C程序中處理線程的1個標準接口。

  • 在大多數(shù)Unix系統(tǒng)可用
  • 允許程序創(chuàng)建,殺死和回收線程,與對等線程安全同享數(shù)據(jù),還可以通知對等線程系統(tǒng)狀態(tài)的變化。

這是我們第1個線程化的代碼,仔細解析。

  • 線程的代碼和本地數(shù)據(jù)被封裝在1個線程例程(thread routine)中。

    • 如第2行代碼所示:每一個線程例程都以1個通用指針作為輸入,并返回1個通用指針。
    • 如果想傳遞多個參數(shù)給線程例程

      • 你應(yīng)當將參數(shù)放到1個結(jié)構(gòu)中。
      • 并傳遞1個指向該結(jié)構(gòu)的指針
    • 如果想要線程例程返回多個參數(shù)。

      • 也能夠返回1個指向結(jié)構(gòu)的指針。
  • tid寄存對等線程的線程ID。

  • 主線程調(diào)用pthread_create函數(shù)創(chuàng)建1個新的對等線程(第7行)。

    • 當對pthread_create的調(diào)用返回時,主線程和新創(chuàng)建的對等線程同時運行。
  • 通過調(diào)用pthread_join,主線程等待對等線程的終止。

  • 對等線程輸出Hello,world。

  • 主線程終止。

12.3.3 創(chuàng)建線程

線程通過調(diào)用pthread_create函數(shù)來創(chuàng)建其他線程。

#include<phread.h>
typedef void *(func)(void *);

int phread_create(pthread_t *tid,pthread_attr_t *attr,fun *f,void *arg)

                    //若成功則返回0,出錯則為非0

pthread_create函數(shù)創(chuàng)建1個新的線程。

  • 帶著1個輸入變量arg,在新線程的上下文中運行線程例程f.
  • 能用attr參數(shù)改變新創(chuàng)建線程的默許屬性。

    • 改變這些屬性超過我們的學(xué)習(xí)范圍。
    • 我們總是用NULL作為attr的參數(shù)。
  • pthread_create返回時,參數(shù)tid包括新創(chuàng)建線程的ID。

    • 通過調(diào)用pthread_self函數(shù)來取得它自己的線程ID。

12.3.4 終止線程

1個線程是以以下方式之1來終止

  • 當頂層的線程例程返回時,線程會隱式地終止。
  • 通過調(diào)用pthread_exit函數(shù),線程會顯示地終止。

    • 如果主線程調(diào)用pthread_exit.
      • 等待所有其他對等線程終止,然后終止主線程和其他全部進程。
      • 返回值為thread_return
    • 原型以下

      #include<pthread.h>
      
      void pthread_exit(void *thread_return)
          //成功返回0,出錯返回非0
      
  • 某個對等線程調(diào)用Unixexit函數(shù),函數(shù)終止進程和所有與該進程有關(guān)的線程。

  • 對等線程通過以當前線程ID為參數(shù)調(diào)用pthread_cancle函數(shù)來終止當前線程。

    • 原型

      #include<pthread.h>
      
      void pthread_cancle(pthread_t tid);
          //成功返回0,出錯返回非0
      

12.3.5 回收已終止的資源

線程通過調(diào)用pthread_join函數(shù)等待其他進程終止

#include<pthread.h>

int pthread_join(pthread_t tid,void **thread_return);

            //返回,成功則為0,出錯為非0
  • pthread_join函數(shù)會阻塞,知道線程tid終止,將線程返回的(void *)指針賦值給thread_return所指向的位置,然后回收已終止線程占用的存儲器資源。

  • pthread_join不像wait函數(shù)1樣等待任意1個線程的結(jié)束。

    • 使得用不那末直觀的方式,檢測1個進程的終止。
    • Stevens在書中指出這是1個設(shè)計毛病。

12.3.6 分離線程

在任何1個時間點上,線程是可結(jié)合的(joinable)或 是分離的(detached)。

  • 1個可結(jié)合的線程能夠被其他線程收回其資源或殺死。

    • 在被其他線程回收之前,它的存儲器資源是沒有被釋放的。
  • 1個分離的線程是不能被其他線程收回其資源或殺死。

    • 系統(tǒng)自動釋放資源。

pthread_detach函數(shù)分離可結(jié)合線程tid

#include<pthread.h>

int pthread_detach(pthread_t tid);

            返回:若成功則返回0,若出錯則返回非零。

12.3.7 初始化線程

pthread_once函數(shù)允許你初始化與線程例程相干的狀態(tài)。

#include<pthread.h>

pthread_once_t once_control = PTHREAD_INIT;

int phread_once(phread_once_t *once_control,void (*init_routine)(void));
  • once_control變量是1個全局或靜態(tài)變量,總是被初始化為PTHREAD_ONCE_INIT.
  • 當你第1次用參數(shù)once_control調(diào)用pthread_once時,它調(diào)用init_routine。

    • 這是1個沒有輸入?yún)?shù),也不返回甚么的函數(shù)。
  • 第2次,第3次以參數(shù)once_control調(diào)用pthread_once時,啥事也不產(chǎn)生。

    • 意思時僅僅第1次調(diào)用時有效果。
  • 當你需要動態(tài)初始化多個線程同享的全局變量時,pthread_once函數(shù)是很有用的。

12.3.8 1個基于線程的并發(fā)服務(wù)器


  • 注意使用malloc動態(tài)給1個connfdp,否則可能兩個線程援用同1個connfdp的地址。

    • 這叫做競爭
  • 為在線程例程中避免存儲器泄漏,使用分離線程。

  • 還要注意釋放在主線程malloc的變量。

12.4 多線程程序中的同享變量

為了解1個C程序中的1個變量是不是同享,有1些基本的問題要解答

  • 線程的基礎(chǔ)存儲器模型是甚么?
  • 根據(jù)這個模型,變量實例是如何映照到存儲器的?
  • 有多少線程援用這些實例?

為了使同享討論具體化,使用下圖的程序作為示例。

示例程序由1個創(chuàng)建兩個對等線程的主線程組成。主線程傳遞1個唯1的ID給每一個對等線程,每一個對等線程利用這個ID輸出1個個性化的信息,和調(diào)用該線程例程的總次數(shù)。

12.4.1 線程存儲器模型

12.4.2 將變量映照到存儲器

線程化的C程序中的變量根據(jù)它們的存儲類型被映照到虛擬存儲器:

  • 全局變量

    • 全局變量是定義在函數(shù)以外的變量。
      • 在運行時,虛擬存儲器中的讀/寫區(qū)域包括每一個全局變量的1個實例。
      • 任何線程都可以援用。
      • 例如,第5行聲明的ptr。
  • 本地自動變量

    • 本地自動變量就是定義在函數(shù)內(nèi)部但是沒有static屬性的變量。
      • 在運行時,每一個線程的包括它自己的所有本地自動變量的實例。
  • 本地靜態(tài)變量

    • 本地靜態(tài)變量是定義在函數(shù)內(nèi)部有static屬性的變量。
      • 和全局變量1樣,存儲在虛擬存儲器的讀/寫區(qū)域。
      • 例如:第25行的cnt.

12.4.3 同享變量

我們說1個變量v同享的,當期僅當它的1個實例被1個以上的線程援用。

例如:

  • cnt 是同享的
  • myid 不是同享的
  • 認識到msgs這類本地自動變量也能被同享是很重要的。

12.5 用信號量同步線程

同享變量10分方便,但是他們也引入了同步毛病(synchronization error)的可能性。

斟酌下圖的程序。

到底哪里出錯了呢?這個毛病10分隱晦,必須通過研究計數(shù)器循環(huán)時的匯編代碼才能看出。

badcnt.c中的兩個對等線程在1個單處理器上并發(fā)履行,機器指令以某種順序1個接1個地完成。同1個程序每次運行的順序都可能不同,這些順序中有1些將會產(chǎn)生正確結(jié)果,但是其他的不會。這就是同步毛病

關(guān)鍵點: 1般而言,你沒有辦法預(yù)測操作系統(tǒng)是不是將為你的線程選擇1個正確的順序。

  • 下圖,就是cnt正確的順序和毛病的順序(正確結(jié)果cnt=2,毛病結(jié)果cnt=1)

我們可以借助于1種叫做進度圖(progress graph)的方法來闡明這些正確和不正確的指令順序的概念。將在接下來介紹。

12.5.1 進度圖

進度圖(process graph)n個并發(fā)進程的履行模型化為1條n維笛卡爾空間的軌跡線。

  • 每條軸k對應(yīng)于k的進度。

  • 每一個點(I1,I2,I3,I4...,In)代表線程k(k=1,...,n)已完成到了Ik這條指令的狀態(tài)。

  • 圖的原點對應(yīng)于沒有任何線程完成這1條指令的初始狀態(tài)。


進度圖將指令履行模型化為從1個狀態(tài)到另外一個狀態(tài)的轉(zhuǎn)換(transition)。

  • 轉(zhuǎn)換指從1點到相鄰1點的有向邊。
    • 合法的轉(zhuǎn)換是向各個軸的正半軸走。

臨界區(qū)

對線程i,操作同享變量cnt內(nèi)容的指令(Li,Ui,Si)構(gòu)成了1個(關(guān)于同享變量cnt的)臨界區(qū)(critical section)。(必須確保指令要這樣履行)

  • 這個臨界區(qū)不應(yīng)當和其他線程的臨界區(qū)交替履行。(這1段的指令不能交叉)。

  • 我們要確保每一個線程在履行它的臨界區(qū)中的指令時,具有對同享變量的互斥的訪問(mutually exclusive access)。

    • 通常這類現(xiàn)象叫做互斥(mutual exclusion)。

不安全區(qū)

在進程圖中,兩個臨界區(qū)的交集情勢稱為不安全區(qū)(unsafe region)

  • 不安全區(qū)邊沿的不算不安全區(qū)的1部份。

安全軌跡線,不安全軌跡線

  • 繞過不安全區(qū)的軌跡線叫做安全軌跡線
    • 能正確更新計數(shù)器
  • 接觸到不安全的軌跡線叫做不安全軌跡線。

我們必須以某種方式同步線程,使它們總是有1條安全軌跡線

  • 1個經(jīng)典的方法,就是基于信號量的思想。

12.5.2 信號量

Edsger Dijksta,并發(fā)編程領(lǐng)域的先鋒任務(wù),提出了1種經(jīng)典的解決同步不同履行線程問題的方法

這類方法是基于1種叫做信號量(semaphore)的特殊類型變量。

  • 信號量s是具有非負整數(shù)值的全局變量。

  • 只能由兩種特殊的操作來處理,這兩種操作稱為PV

    • P(s),Proberen,測試

      • 如果s是非零的,那末P操作s減1,并且立即返回。
      • 如果s為零,那末就掛起這個線程,直到s變成非零。
        • 而1個V操作會重啟這個線程。
        • 在重啟以后,P操作s減1,并將控制返回給調(diào)用者。
    • V(s),Verhogen,增加

      • V操作s加1.
      • 如果有任何線程阻塞在P操作等待s變成非零。
        • 那末V操作隨機會重啟這些線程中的1個。
        • 然后將s減去1,完成它的P操作。
    • 重點,P操作V操作都是不可分割的,也就是本身確保了是1個帶有安全軌跡的操作。(所以又叫原語)

      • 對照,上文中的cnt++的操作。
      • 例如,加1這個操作中,加載,加1,存儲信號量進程是不可分割的。

PV的定義確保了1個正在運行的程序絕不可能進入這樣1種狀態(tài),也就是不可能有負值。
這個屬性叫做信號量不變性(semaphore invariant),為控制并發(fā)程序的軌跡線提供了強有力的工具。

12.5.3 使用信號量來實現(xiàn)互斥

信號量提供了1種很方便的方法來確保對同享變量的互斥訪問。

基本的思想是

  • 將每一個同享變量(或1組相干的同享變量) 與1個信號量s(初始為)`聯(lián)系起來。
  • 然后用P(s)V(s)操作相應(yīng)的臨界區(qū)包圍起來。

以這類方式保護同享變量的信號量叫做2元信號量(binary semaphore)

  • 由于它的值總是0或1。

以提供互斥為目的的2元信號量常常也稱為互斥鎖(mutex)。

  • 在1個互斥鎖上履行P操作叫做互斥鎖加鎖。
  • 在1個互斥鎖上履行V操作叫做互斥鎖解鎖。
  • 對1個互斥鎖加了鎖還沒有解鎖的線程稱為占用這個互斥鎖

1個被用作1組可用資源的計數(shù)器的信號量稱為計數(shù)信號量。

關(guān)鍵思想:

  • P操作V操作的結(jié)合創(chuàng)建了1組狀態(tài),叫做制止區(qū)(forbidden regin),其中s<0
    • 由于信號量的不變形,不可能有軌跡線進入這個區(qū)域
    • 而且制止區(qū)包括了不安全區(qū)的任何部份。
      • 使得,每條可行的軌跡線都是安全的。

代碼上的實現(xiàn)

正確切現(xiàn)上文中的cnt的線程同步。

  • 第1步:聲明1個信號量 mutex

    volatile int cnt = 0 ; 
    sem_t mutex;
    
  • 第2步:主線程中初始化

    Sem_init(&mutex,0,1);
    
  • 第3步,在線程例程中對同享變量cnt的更新包圍PV操作,從而保護了它們。

    for( i = 0 ;i < niters ;i++) {
        P(&mutex);
        cnt++;
        V(&mutex);
    }
    

12.5.4 利用信號量來調(diào)度同享資源

除提供互斥外,信號量的另外一個重要作用是調(diào)度對同享資源的訪問

  • 在這類場景中,1個線程用信號量操作來通知另外一個線程,程序狀態(tài)中的某個條件為真了。
  • 兩個經(jīng)典而有用的例子。

    • 生產(chǎn)者 - 消費者 問題
    • 讀者 - 寫者 問題

1.生產(chǎn)者和消費者

圖給出了生產(chǎn)者消費者問題

  • 生產(chǎn)者線程反復(fù)地生成新的項目,并把它們插入到緩沖區(qū)中。
  • 消費者線程不斷地從緩沖區(qū)取出這些項目,然后消費使用它們。
  • 也可能有多個的變種。

由于插入和取出項目都觸及更新同享變量

  • 所以我們必須保證對緩沖區(qū)的訪問是互斥的
  • 還需要調(diào)度對緩沖區(qū)的訪問。
    • 如果緩沖區(qū)是滿的,那末生產(chǎn)者必須等待直到有1個槽位變成可用。
    • 如果緩沖區(qū)是空的,那末消費者必須等待知道有1個項目變成可用。

我們將開發(fā)1個簡單的包,叫做SBUF,用來構(gòu)造生產(chǎn)者-消費者程序。

SBUF操作類型為sbuf_t的有限緩沖區(qū)。

  • 項目寄存在1個動態(tài)分配的n項整數(shù)數(shù)組(buf)中。
  • frontrear索引值記錄該隊列的第1項和最后1項。
  • 3個信號量同步對緩沖區(qū)的訪問。
    • mutex信號量提供互斥的緩沖區(qū)訪問
    • slotsitems信號量分別記錄空槽位和可用項目的數(shù)量。

以下給出SBUF函數(shù)的實現(xiàn):

  • sbuf_init函數(shù)進行初始。
    • 為緩沖辨別配堆存儲器
    • 設(shè)置frontrear表示1個空的緩沖區(qū)。
    • 并為3個信號量賦初值。

  • sbuf_deinit函數(shù)是當利用程序使用完緩沖區(qū)時,釋放緩沖區(qū)存儲。
  • sbuf_insert

    • 等待1個可用的槽位
    • 對互斥鎖加鎖,添加項目,對互斥鎖解鎖
    • 然后宣布有1個新項目可用。
  • sbuf_remove

    • 等待1個可用的項目
    • 對互斥鎖加鎖,取出項目,對互斥鎖解鎖
    • 然后宣布有1個新槽位可用。

2.讀者-寫者問題

讀者-寫著問題是互斥問題的1個概括。

  • 1組并發(fā)的線程要訪問同1個數(shù)據(jù)對象。

    • 修改對象的線程叫做寫者
    • 只讀對象的線程叫做讀者
  • 寫者必須具有對對象的獨占訪問。

  • 讀者可以和無窮多個其他讀者同享對象。
  • 1般來講有沒有數(shù)個并發(fā)的讀者和寫者。

讀者-寫者問題有幾個變種,都是基于讀者和寫者的優(yōu)先級

  • 第1類讀者-寫者問題

    • 讀者優(yōu)先,要求不要讓讀者等待,除非已把1個使用權(quán)限賦予了1個寫者。
    • 換句話說,讀者不會由于有1個寫者在等待而等待。
  • 第2類讀者-寫者問題(?)

    • 寫者優(yōu)先,要求1但1個寫者準備好可以寫,它就會盡量地完成它的寫操作。
    • 同第1類不同,在1個寫者到達后的讀者必須等待,即便這個寫者也是在等待。

給出第1類讀者-寫者問題答案。

  • 這個的優(yōu)先級很弱,由于1個離開臨界區(qū)的寫者可能重啟1個在等待的寫者(隨機重啟)
    • 很有可能1群寫者使得1個讀者饑餓

  • 信號量w控制對訪問同享對象的臨街區(qū)的訪問。

    • 讀者

      • w只對第1個讀者上鎖
      • w對最后1個走的讀者解鎖
    • 寫者

      • 寫者只要進入臨界區(qū)就對w上鎖
      • 寫者只要離開臨界區(qū)就對w解鎖
  • 信號量mutex保護對同享變量readcnt的訪問。
    • readcnt統(tǒng)計當前臨界區(qū)的讀者數(shù)量。

所有讀者-寫者答案都有可能致使饑餓

  • 饑餓就是1個線程無窮期地阻塞,沒法進展。

12.5.5 基于預(yù)線程化的并發(fā)服務(wù)器

為每一個新的客戶端創(chuàng)建新的線程,有很多的代價。

1個基于預(yù)線程化服務(wù)器利用生產(chǎn)者-消費者模型構(gòu)造1個更高效力的方式。

  • 生產(chǎn)者: 主線程
  • 消費者: 對等線程

12.6 使用線程提高并行性(暫略)

主要用于多核CPU的算法。

比如:利用并行來完成n路遞歸

12.7 其他并提問題

互斥生產(chǎn)者-消費者同步的技術(shù),只是并提問題的冰山1角。

同步問題從根本來講是很難的問題。

這章我們以線程為例討論。

  • 但是要知道同步問題在任何并發(fā)流操作同享資源時都會出現(xiàn)。
    • 比如之前學(xué)信號時,回收進程時的競爭

12.7.1 線程安全

1個函數(shù)被稱為線程安全的(thread-safe),當且僅當被多個并發(fā)線程反復(fù)地調(diào)用時,它會1直產(chǎn)生正確的結(jié)果。否則就是線程不安全的(thread-unsafe)

我們能夠定義出4個(不相交)線程不安全函數(shù)類:

  • 第 1 類 : 不保護同享變量的函數(shù)。
    • 解決方案,利用P,V這樣的同步操作來保護同享的變量
  • 第 2 類 : 保持逾越多個調(diào)用狀態(tài)的函數(shù)

    • 1個偽隨機數(shù)生成器是這類線程不安全的例子。

    • 由于產(chǎn)生的結(jié)果依賴于上1個next的值。

      • 在單線程中,用同1個seed不管運行多少次,都是一樣的結(jié)果。
      • 多線程中,這類情況就不會出現(xiàn)了,所以是線程不安全
    • 解決方案: 重寫

      • 使得它不能依賴static,而是依托調(diào)用者在參數(shù)中傳遞狀態(tài)信息。
      • 缺點: 需要在曾成千上百個不同的調(diào)用位置,修改。10分麻煩。
  • 第 3 類 :返回指向靜態(tài)變量的指針的函數(shù)( 有點類似第1類 )

    • 危害:我們在并發(fā)線程中調(diào)用這些函數(shù),可能產(chǎn)生災(zāi)害。
      • 由于1個正在被1個線程使用的變量,可能偷偷被另外一個線程悄悄覆蓋。
    • 解放方案

      • 重寫函數(shù):讓調(diào)用者傳遞寄存的結(jié)果的指針。
      • 加鎖-拷貝技術(shù):

        • 在1個調(diào)用位置,互斥鎖加鎖。
        • 調(diào)用線程不安全函數(shù),將函數(shù)返回的結(jié)構(gòu)拷貝到1個私有的存儲器。
        • 然后互斥鎖,解鎖。
      • 用上面的原理寫1個線程不安全函數(shù)的包裝函數(shù)來實現(xiàn)線程安全。

        • 以ctime為例子

  • 第 4 類 : 調(diào)用線程不安全函數(shù)的函數(shù)。

    • 如果函數(shù)f調(diào)用線程不安全函數(shù)g。那末f可能不安全。
      • 如果g是第2類,那末f1定不安全,也沒有辦法去修正,只能改變g.
      • 如果g是第1,3類,可以用加鎖-拷貝技術(shù)來解決。

12.7.2 可重入性

有1類重要的線程安全函數(shù),叫做可重入函數(shù)(reentrant function)

  • 其特點在于它們有這樣1種屬性。

    • 當它們被多個線程調(diào)用時,不會援用任何同享數(shù)據(jù)。
  • 被分為兩類

    • 顯示可重入
      • 參數(shù)都是值傳遞
      • 變量都是本地自動棧變量
    • 隱式可重入

      • 參數(shù)可以有指針

        • 但是不允許調(diào)用者傳入指向同享數(shù)據(jù)的指針。
      • 是不是可重入,同時取決于調(diào)用者,和被調(diào)用者。

  • 可重入函數(shù)比較高效是由于不需要同步操作。

  • 認識到可重入性有時即是調(diào)用者也是被調(diào)用者的屬性。

    • 其實不是被調(diào)用者的單獨屬性。

12.7.3 在線程化的程序中使用已存在的庫函數(shù)

大多數(shù)Unix函數(shù),包括大部份定義在標準C庫的函數(shù)(malloc,free,realloc,printfscanf)都是線程安全的。

部份線程不安全

  • asctime,ctime,localtime函數(shù)是在不同時間和數(shù)據(jù)格式相互來回轉(zhuǎn)換時常常使用的函數(shù)。

  • gethostbyname,gethostbyaddr,inet_ntoa函數(shù)是常常用的網(wǎng)絡(luò)編程函數(shù)。

  • strtok函數(shù)是1個過時了的同來分析字符串的函數(shù)。


Unix系統(tǒng)提供大多數(shù)線程不安全函數(shù)的可重入版本。

  • 可重入的版本總是以 _r后綴結(jié)尾。
  • 例,gethostbyname_r

12.7.4 競爭

當1個程序的正確性依賴于1個線程要在另外一個線程到達y點之前到達它的控制流中的x點,就會產(chǎn)生競爭

  • 通常,競爭產(chǎn)生的理由是由于程序員假定某種特殊的軌跡線穿過履行狀態(tài)空間。

例子:

程序10分簡單。

主線程創(chuàng)建了4個對等線程,并傳遞1個指向循環(huán)變量i的指針作為線程的ID。并輸出。

  • 1般而言,循環(huán)變量i1定是4個不同的。所以會想固然覺得會輸出4個不同的ID。

  • 但是從結(jié)果來看,明顯是毛病的,有兩個3,為何?
    • 由于我們想固然的覺得對等線程myid賦值結(jié)束后,i才會自增。
    • 競爭來源于 主線程中i++,和對等線程myid=*((int *)vargp)競爭。

解決方案:用1個臨時地址保存i

12.7.5 死鎖

信號量引入了1種潛伏的使人討厭的運行時毛病,叫做死鎖 (deadlock)。

  • 指的是1組線程被阻塞,等待1個永久不為真的條件。

進度圖對理解死鎖是1個無價的工具。

  • 死鎖的區(qū)域d是1個只能進不能出的區(qū)域。

    • 位置是合法的,其實不是制止區(qū),能進去。
    • 但是會發(fā)現(xiàn)不管向上,還是右,都只剩下制止區(qū)了。
  • 如果制止區(qū)不堆疊,1定不會產(chǎn)生死鎖。

    • 否則,可能產(chǎn)生死鎖。
  • 死鎖是1個相當困難的問題,由于它總是不可預(yù)測的。

    • 榮幸的話,會繞開死鎖區(qū)域。
    • 毛病還不會重復(fù),軌跡不同。

特殊解

使用2元信號量來實現(xiàn)互斥,可以利用1下有效的規(guī)則。

互斥鎖加鎖順序規(guī)則:如果對程序中每對互斥鎖(s,t),每一個占用st的線程都依照相同的順序?qū)λ鼈兗渔i,那末這個程序就是無死鎖的。


GGGGGGGGGGG,暫時告1段落了!!!!!!!!!!!!!!ddd!!

生活不易,碼農(nóng)辛苦
如果您覺得本網(wǎng)站對您的學(xué)習(xí)有所幫助,可以手機掃描二維碼進行捐贈
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關(guān)閉
程序員人生
主站蜘蛛池模板: 精品久久www | 欧美日韩精品久久久久 | 亚洲黄色免费 | 2024国产精品视频 | 久久国产精品久久久久久久久久 | 精品久| 国产成人无遮挡在线视频 | 久久久91 | 久久激情综合网 | 亚洲一区二区三区中文字幕 | 成人高清av| 九九热视频在线 | 爱情岛论坛在线观看 | 欧美日韩成人精品 | 国产精品亚洲一区二区三区在线观看 | 一区二区毛片 | 高清国产一区二区 | 好av在线| 亚洲成人av一区 | 久久国产欧美一区二区三区免费 | 日本欧美久久久 | 国产精品日韩欧美一区二区 | 国产视频观看 | 日韩一区二区三区在线视频 | 8x拔播拔播国产在线视频 | 午夜精品久久久久久久白皮肤 | 日本一区二区三区四区 | 一区二区三区免费 | 国产九色 | 99热在线只有精品 | 国产精品一区二区在线 | 人人草人人干 | 久久久成人av| 日韩中文字幕av | 天堂网avav| 正在播放一区 | 亚洲在线免费观看 | 精品美女久久 | 91九色论坛 | 久久久www成人免费精品 | 日韩欧美综合 |