1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        『每周譯Go』以Go為例-探究并行與并發(fā)的區(qū)別

        共 2385字,需瀏覽 5分鐘

         ·

        2022-02-12 10:03

        在軟件內并行是指多條指令同時執(zhí)行。每個編程語言都有各自實現(xiàn)并行,或者像Go,將并行作為語言的一部分,提供原生支持。并行讓軟件工程師能夠同時在多核處理器上并行執(zhí)行任務,從而拋開硬件的物理限制。1

        通常情況下,由于構建并行模塊的復雜性,一個應用程序的并行程度取決于工程師編寫軟件的能力。

        并行任務的例子:

        • 多人同時在餐廳點單
        • 多個收銀員在雜貨鋪
        • 多核CPU

        事實上,在任何一個應用程序中都有多層含義的并行。有應用程序本身的并行,這是由應用程序開發(fā)人員定義的,還有由操作系統(tǒng)協(xié)調的物理硬件上的CPU執(zhí)行的指令的并行(或復用)。

        注意:一般情況下,應用程序必須明確寫出他們使用并行。這個需要工程師需要有技能寫出”正確”的可并行的代碼。

        構建并行

        應用程序開發(fā)人員利用抽象概念來描述一個應用程序的并行。這些抽象概念通常在每個實現(xiàn)并行的編程語言上會有所不同,但是概念是一樣的。舉個例子,在C語言,并行是通過pthread來定義的。在Go,并行是通過goroutines來定義的。

        進程

        一個進程是一個單一的執(zhí)行單元,包含它自己的”程序計數(shù)器,寄存器和變量”。從概念上來講,每個進程有它自己的虛擬CPU”2。這一點很重要,因為涉及到進程在創(chuàng)建和管理過程中的開銷。除了創(chuàng)建進程時的開銷,每個進程只允許訪問自己的內存。這表示進程不能訪問其他進程的內存。

        如果多個執(zhí)行線程(并行任務)需要訪問一些共享資源時,這會是一個問題。

        線程

        線程是作為一種方法被引入的,它允許在同一進程中訪問共享內存,但在不同的并行執(zhí)行單元上。線程基本上是自己的進程,但是可以訪問父進程的共享地址空間。

        線程相較于進程只需要更少的開銷,因為它們不需要為了每個線程創(chuàng)建新進程,并且資源可以被共享或者復用。

        這里有一個在Ubuntu 18.04下,克隆進程和創(chuàng)建線程的開銷比較:3

        #?Borrowed?from?https://stackoverflow.com/a/52231151/834319
        #?Ubuntu?18.04?start_method:?fork
        #?================================
        results?for?Process:

        count????1000.000000
        mean????????0.002081
        std?????????0.000288
        min?????????0.001466
        25%?????????0.001866
        50%?????????0.001973
        75%?????????0.002268
        max?????????0.003365?

        Minimum?with?1.47?ms
        ------------------------------------------------------------

        results?for?Thread:

        count????1000.000000
        mean????????0.000054
        std?????????0.000013
        min?????????0.000044
        25%?????????0.000047
        50%?????????0.000051
        75%?????????0.000058
        max?????????0.000319?

        Minimum?with?43.89?μs
        ------------------------------------------------------------
        Minimum?start-up?time?for?processes?takes?33.41x?longer?than?for?threads.

        臨界區(qū)

        臨界區(qū)是共享的內存部分,它被進程中的各種并行任務所需要。這個部分可能是共享數(shù)據(jù),類型或者資源。(見下方的范例4)

        并行的復雜性

        由于一個進程的線程在同一內存空間中執(zhí)行,因此存在著臨界區(qū)被多個線程同時訪問的風險。在應用程序中這個可能導致數(shù)據(jù)損壞或其他無法預料的行為。

        這里有2個主要問題當多個線程同一時間訪問共享內存的時候。

        競態(tài)條件

        舉個例子,想象一個進程的線程正在從一個共享內存地址讀取一個數(shù)值,同時其他線程正在往同一個地址寫一個新的數(shù)值。如果第一個線程在第二個線程寫數(shù)值之前讀取了數(shù)值,第一個線程就會讀取到舊的數(shù)值。

        這會導致應用程序出現(xiàn)不符合預期的情況。

        死鎖

        當兩個或多個線程在互相等待對方做某事時,就會出現(xiàn)死鎖。這會導致應用程序掛起或者崩潰。

        有一個例子是這樣的,當一個線程等待一個時機去執(zhí)行臨界區(qū)的同時,另一個線程也正在等待其他線程滿足條件后去執(zhí)行相同的臨界區(qū)。如果第一個線程正在等待滿足時機,然后第二個線程也正在等待第一個線程,那這兩個線程將一直等待下去。

        第二種形式的死鎖會發(fā)生在嘗試使用互斥鎖保護競態(tài)。

        屏障

        屏障可以稱為一個同步點,它管理一個進程中多個線程對共享資源或臨界區(qū)的訪問。

        這些屏障允許應用程序開發(fā)者去控制并行訪問,從而保證資源不會在不安全的情況下被訪問。

        互斥鎖(Mutexes)

        互斥鎖是屏障的一個類型,它只允許一個線程在同一時間訪問共享資源。這對于防止在讀取或寫入共享資源時通過鎖定和解鎖出現(xiàn)競態(tài)的情況非常有用。

        //?Example?of?a?mutex?barrier?in?Go
        import?(
        ??"sync"
        ??"fmt"
        )

        var?shared?string
        var?sharedMu?sync.Mutex

        func?main()?{

        ??//?Start?a?goroutine?to?write?to?the?shared?variable
        ??go?func()?{
        ????for?i?:=?0;?i???????write(fmt.Sprintf("%d",?i))
        ????}
        ??}()

        ??//?read?from?the?shared?variable
        ??for?i?:=?0;?i?????read(fmt.Sprintf("%d",?i))
        ??}
        }

        func?write(value?string)?{
        ??sharedMu.Lock()
        ??defer?sharedMu.Unlock()

        ??//?set?a?new?value?for?the?`shared`?variable
        ??shared?=?value
        }

        func?read()?{
        ??sharedMu.Lock()
        ??defer?sharedMu.Unlock()

        ??//?print?the?critical?section?`shared`?to?stdout
        ??fmt.Println(shared)
        }

        如果我們看上面的例子,我們可以看到shared變量被互斥鎖保護著。這意味著只有一個線程在一個時間點可以訪問shared變量。這個保證了shared變量不被損壞,并且是一個可預計的行為。

        注意: 在使用互斥鎖時,需要注意的一個點是,要在函數(shù)返回的時候釋放互斥鎖。在Go,舉個例子,這個操作可以通過關鍵字defer實現(xiàn)。這個保證了其他線程可以訪問到共享資源。

        信號量

        信號量是一種類型的屏障,允許一個時間點一定數(shù)量的線程訪問共享資源。這個和互斥鎖的區(qū)別在于,訪問資源的線程數(shù)量不會被限制為1個。

        在Go標準庫沒有信號的實現(xiàn),但是可以通過channels5來實現(xiàn)。

        忙等待

        忙等待是一個技術用于線程等待一個滿足的條件。通常用于等待一個計數(shù)器達到某個數(shù)值。

        //?Example?of?Busy?Waiting?in?Go
        var?x?int

        func?main()?{
        ??go?func()?{
        ????for?i?:=?0;?i???????x?=?i
        ????}
        ??}()

        ??for?x?!=?1?{?//?Loop?until?x?is?set?to?1
        ????fmt.Println("Waiting...")
        ????time.Sleep(time.Millisecond?*?100)
        ??}??
        }

        因此,忙等待需要一個等待條件滿足的循環(huán),該循環(huán)對共享資源進行讀取或寫入,必須由一個互斥鎖來保護以確保正確的行為。

        上面例子的問題是那個循環(huán)在訪問一個沒有被互斥鎖保護的臨界區(qū)。這可能導致競態(tài),這個循環(huán)讀取的數(shù)值可能已經(jīng)被另一個進程里的線程修改了。事實上,上面的例子是一個很好的競態(tài)例子。很有可能這個應用程序永遠都不會退出,因為無法保證這個循環(huán)是否會足夠快地讀取到x的數(shù)值,同時讀取出來的數(shù)值都是1,這就意味著循環(huán)永遠不會退出。

        如果我們要用互斥鎖保護變量x,那么循環(huán)就會被保護并且應用程序會退出,但這仍然不完美,設置x的循環(huán)仍然可以快到在讀取值的循環(huán)執(zhí)行之前擊中互斥鎖兩次(盡管不太可能)。

        import?"sync"

        var?x?int
        var?xMu?sync.Mutex

        func?main()?{
        ??go?func()?{
        ????for?i?:=?0;?i???????xMu.Lock()
        ??????x?=?i
        ??????xMu.Unlock()
        ????}
        ??}()

        ??var?value?int
        ??for?value?!=?1?{?//?Loop?until?x?is?set?to?1
        ????xMu.Lock()
        ????value?=?x?//?Set?value?==?x
        ????xMu.Unlock()
        ??}??
        }

        通常情況下忙等待不是一個好的想法。最好的辦法是使用信號或者一個互斥鎖去確保臨界區(qū)是受保護的。我們將介紹在Go中處理這個問題的更好方法,但它說明了編寫 “正確的”可并行代碼的復雜性。

        等待組(Wait Groups)

        等待組是一個用來保證所有并行代碼路徑在繼續(xù)之前完成處理的方法。在Go里,這個用標準庫中的sync包中提供的sync.WaitGroup來實現(xiàn)。

        //?Example?of?a?`sync.WaitGroup`?in?Go
        import?(
        ??"sync"
        )

        func?main()?{
        ??var?wg?sync.WaitGroup
        ??var?N?int?=?10

        ??wg.Add(N)
        ??for?i?:=?0;?i?????go?func()?{
        ??????defer?wg.Done()
        ??????
        ??????//?do?some?work??????
        ????}()
        ??}

        ??//?wait?for?all?of?the?goroutines?to?finish
        ??wg.Wait()
        }

        在上面這個例子的wg.Wait()是一個阻塞調用。這個表示主線程會等到所有協(xié)程完成后再繼續(xù)執(zhí)行,并且對應的defer wg.Done()已經(jīng)被調用。WaitGroup的內部實現(xiàn)是一個計數(shù)器,當每個協(xié)程在調用wg.Add(N)后會加1,同時協(xié)程被加到WaitGroup內。當計數(shù)器計到0,主線程會繼續(xù)執(zhí)行或者在這個例子中會退出。

        什么是并發(fā)?

        并發(fā)和并行經(jīng)常混為一談。為了更好地理解并發(fā)和并行的區(qū)別,讓我們看一個現(xiàn)實生活中的并發(fā)例子。

        如果我們用餐廳來當做例子,餐廳里面會有幾種不同工作類型(或可復制的程序)的組別。

        1. 接待(負責為客人安排座位)
        2. 服務員(負責接單,并提供食物)
        3. 廚房(負責烹飪食物)
        4. 售貨員(負責清理桌子
        5. 洗碗工(負責清理餐具) 每個組別負責不同的任務,所有這些任務的最終結果都是讓顧客吃到一頓飯。這稱之為并發(fā),專門的工作中心,可以專注于單獨的任務,這些任務結合起來就會產(chǎn)生一個結果。

        如果餐廳只雇傭一個員工來做所有的任務,這對于一個高效率的餐廳是一個限制。這稱之為序列化。如果在餐廳里只有一個服務員,那么在一個時間只能夠處理一個訂單。

        并行性是指將并發(fā)的任務分配到多個資源上的能力。在餐廳中,這可能會包含服務,食物準備和清理。如果有多個服務員,那么同一時間就可以處理多個訂單。

        每個組可以專注在他們自己的工作中心,不需要擔心上下文切換,最大吞吐量,或最小延遲。

        其他有同時進行的工作中心的行業(yè)例子包括工廠工人和裝配線工人。從本質上講,任何可以被分解成較小的可重復任務的過程都可以被認為是并發(fā)的,因此當使用合適的并發(fā)設計的時候可以被并行處理。

        TL:DR:并發(fā)實現(xiàn)正確的并行,但是并行對并發(fā)代碼不是必要的。6


        1. Andrew S. Tanenbaum and Herbert Bos, Modern Operating Systems (Boston, MA: Prentice Hall, 2015), 517. ??
        2. Andrew S. Tanenbaum and Herbert Bos, Modern Operating Systems (Boston, MA: Prentice Hall, 2015), 86. ??
        3. Benchmarking Process Fork vs Thread Creation on Ubuntu 18.04
          (https://stackoverflow.com/a/52231151/834319)??
        4. Flowgraph description of critical section - Kartikharia
          (https://commons.wikimedia.org/wiki/File:Critical_section_fg.jpg)??
        5. Example semaphore implementation in Go
          (http://www.golangpatterns.info/concurrency/semaphores) ??
        6. https://youtu.be/oV9rvDllKEg??



        原文信息

        原文地址:https://benjiv.com/parallelism-vs-concurrency/

        原文作者:Benjamin Vesterby

        本文永久鏈接:https://github.com/gocn/translator/blob/master/2022/w05_Parallelism_and_Concurrency_What’s_the_Difference.md

        譯者:zxmfke

        校對:Cluas




        想要了解關于 Go 的更多資訊,還可以通過掃描的方式,進群一起探討哦~



        瀏覽 55
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            一级黄电影| 成人性爱网址 | 大色天堂 | 成人电影91 | 沈樵精品国产成av片 | 色国产精品女五丁香五月五月 | 婷婷综合久久一区二区三区 | 伊人中文字幕 | 操B在线播放 | 最新天堂地址 |