• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            Fork me on GitHub
            隨筆 - 215  文章 - 13  trackbacks - 0
            <2025年5月>
            27282930123
            45678910
            11121314151617
            18192021222324
            25262728293031
            1234567


            專注即時通訊及網(wǎng)游服務(wù)端編程
            ------------------------------------
            Openresty 官方模塊
            Openresty 標(biāo)準(zhǔn)模塊(Opm)
            Openresty 三方模塊
            ------------------------------------
            本博收藏大部分文章為轉(zhuǎn)載,并在文章開頭給出了原文出處,如有再轉(zhuǎn),敬請保留相關(guān)信息,這是大家對原創(chuàng)作者勞動成果的自覺尊重!!如為您帶來不便,請于本博下留言,謝謝配合。

            常用鏈接

            留言簿(1)

            隨筆分類

            隨筆檔案

            相冊

            Awesome

            Blog

            Book

            GitHub

            Link

            搜索

            •  

            積分與排名

            • 積分 - 215411
            • 排名 - 118

            最新評論

            閱讀排行榜

            本文轉(zhuǎn)自:http://blog.csdn.net/yue7603835/article/details/44309409
            轉(zhuǎn)載請自覺標(biāo)明原文出處
            Golang :不要通過共享內(nèi)存來通信,而應(yīng)該通過通信來共享內(nèi)存。這句風(fēng)靡在Go社區(qū)的話,說的就是 goroutine中的 channel .......
            他在go并發(fā)編程中充當(dāng)著 類型安全的管道作用。
            1、通過golang中的 goroutine 與sync.Mutex進(jìn)行 并發(fā)同步
            import( 
                "fmt"
                "sync"
                "runtime"
            )
            var count int =0;
            func counter(lock * sync.Mutex){
                  lock.Lock()
                  count++
                  fmt.Println(count)
                  lock.Unlock()
            }
            func main(){
               lock:=&sync.Mutex{}
               for i:=0;i<10;i++{
                  //傳遞指針是為了防止 函數(shù)內(nèi)的鎖和 調(diào)用鎖不一致
                  go counter(lock)  
                 }
               for{
                  lock.Lock()
                  c:=count
                  lock.Unlock()
                  ///把時間片給別的goroutine  未來某個時刻運(yùn)行該routine
                  runtime.Gosched()
                  if c>=10{
                    fmt.Println("goroutine end")
                    break
                    }
               }    
            }
            2、goroutine之間通過 channel進(jìn)行通信,channel是和類型相關(guān)的 可以理解為  是一種類型安全的管道。
            簡單的channel 使用
            package main  
            import "fmt"
            func Count(ch chan int) {
                ch <- 1  
                fmt.Println("Counting")
            }
            func main() {
                chs := make([]chan int, 10)
            for i := 0; i < 10; i++ {
                    chs[i] = make(chan int)
              go Count(chs[i])
              fmt.Println("Count",i)
                }
            for i, ch := range chs {
              <-ch
              fmt.Println("Counting",i)
                }  
            3、Go語言中的select是語言級內(nèi)置  非堵塞
            select {
            case <-chan1: // 如果chan1成功讀到數(shù)據(jù),則進(jìn)行該case處理語句  
            case chan2 <- 1: // 如果成功向chan2寫入數(shù)據(jù),則進(jìn)行該case處理語句  
            default: // 如果上面都沒有成功,則進(jìn)入default處理流程  
            }
            可以看出,select不像switch,后面并不帶判斷條件,而是直接去查看case語句。每個
            case語句都必須是一個面向channel的操作。比如上面的例子中,第一個case試圖從chan1讀取
            一個數(shù)據(jù)并直接忽略讀到的數(shù)據(jù),而第二個case則是試圖向chan2中寫入一個整型數(shù)1,如果這
            兩者都沒有成功,則到達(dá)default語句。 
            4、channel 的帶緩沖讀取寫入
            之前我們示范創(chuàng)建的都是不帶緩沖的channel,這種做法對于傳遞單個數(shù)據(jù)的場景可以接受,
            但對于需要持續(xù)傳輸大量數(shù)據(jù)的場景就有些不合適了。接下來我們介紹如何給channel帶上緩沖,
            從而達(dá)到消息隊列的效果。
            要創(chuàng)建一個帶緩沖的channel,其實也非常容易:
            c := make(chan int, 1024)
            在調(diào)用make()時將緩沖區(qū)大小作為第二個參數(shù)傳入即可,比如上面這個例子就創(chuàng)建了一個大小
            為1024的int類型channel,即使沒有讀取方,寫入方也可以一直往channel里寫入,在緩沖區(qū)被
            填完之前都不會阻塞。
            從帶緩沖的channel中讀取數(shù)據(jù)可以使用與常規(guī)非緩沖channel完全一致的方法,但我們也可
            以使用range關(guān)鍵來實現(xiàn)更為簡便的循環(huán)讀取:
            for i := range c {
                fmt.Println("Received:", i)
            5、用goroutine模擬生產(chǎn)消費(fèi)者
            package main
            import "fmt"
            import "time"
            func Producer (queue chan<- int){
                    for i:= 0; i < 10; i++ {
                            queue <- i  
                            }
            }
            func Consumer( queue <-chan int){
                    for i :=0; i < 10; i++{
                            v := <- queue
                            fmt.Println("receive:", v)
                    }
            }
            func main(){
                    queue := make(chan int, 1)
                    go Producer(queue)
                    go Consumer(queue)
                    time.Sleep(1e9) //讓Producer與Consumer完成
            }
            6、 通過make 創(chuàng)建通道 
             make(c1 chan int)   創(chuàng)建的是 同步channel ...讀寫完全對應(yīng)
            make(c1 chan int ,10) 闖進(jìn)帶緩沖的通道 上來可以寫10次
            7、隨機(jī)向通道中寫入0或者1 
            package main
            import "fmt"
            import "time"
            func main(){
                   ch := make(chan int, 1)
             for {
               ///不停向channel中寫入 0 或者1
              select {
               case ch <- 0:
               case ch <- 1:
              }
                //從通道中取出數(shù)據(jù)
                i := <-ch
                fmt.Println("Value received:",i)
                time.Sleep(1e8)
                }
            }
            8、帶緩沖的channel 
            之前創(chuàng)建的都是不帶緩沖的channel,這種做法對于傳遞單個數(shù)據(jù)的場景可以接受,
            但對于需要持續(xù)傳輸大量數(shù)據(jù)的場景就有些不合適了。接下來我們介紹如何給channel帶上緩沖,
            從而達(dá)到消息隊列的效果。
            要創(chuàng)建一個帶緩沖的channel,其實也非常容易:
            c := make(chan int, 1024)
            在調(diào)用make()時將緩沖區(qū)大小作為第二個參數(shù)傳入即可,比如上面這個例子就創(chuàng)建了一個大小
            為1024的int類型channel,即使沒有讀取方,寫入方也可以一直往channel里寫入,在緩沖區(qū)被
            填完之前都不會阻塞。
            從帶緩沖的channel中讀取數(shù)據(jù)可以使用與常規(guī)非緩沖channel完全一致的方法,但我們也可
            以使用range關(guān)鍵來實現(xiàn)更為簡便的循環(huán)讀取:
            for i := range c {
                fmt.Println("Received:", i)
            }
            ////////////////////////////////////////下面是測試代碼////////////////////////////////////
            package main
            import "fmt"
            import "time"
            func A(c chan int){
             for i:=0;i<10;i++{
                    c<- i
                }
            }
            func B(c chan int){
             for val:=range c {
                  fmt.Println("Value:",val)  
                }
            }
            func main(){
                chs:=make(chan int,10)
                //只要有通道操作一定要放到goroutine中否則 會堵塞當(dāng)前的主線程 并且導(dǎo)致程序退出
                //對于同步通道 或者帶緩沖的通道 一定要封裝成函數(shù) 使用 goroutine 包裝
                go A(chs)
                go B(chs)
                time.Sleep(1e9)
            }
            9、關(guān)于創(chuàng)建多個goroutine具體到go語言會創(chuàng)建多少個線程
            import "os"
            func main() {
                for i:=0; i<20; i++ {
                    go func() {
                        for {
                            b:=make([]byte, 10)
                            os.Stdin.Read(b) // will block
                        }
                    }()
                }
                select{}
            }
            會產(chǎn)生21個線程:
            runtime scheduler(src/pkg/runtime/proc.c)會維護(hù)一個線程池,當(dāng)某個goroutine被block后,scheduler會創(chuàng)建一個新線程給其他ready的goroutine
            GOMAXPROCS控制的是未被阻塞的所有g(shù)oroutine被multiplex到多少個線程上運(yùn)行
            10、在channel中也是可以傳遞channel的,Go語言的channel和map  slice等一樣都是原生類型
            需要注意的是,在Go語言中channel本身也是一個原生類型,與map之類的類型地位一樣,因
            此channel本身在定義后也可以通過channel來傳遞。
            我們可以使用這個特性來實現(xiàn)*nix上非常常見的管道(pipe)特性。管道也是使用非常廣泛
            的一種設(shè)計模式,比如在處理數(shù)據(jù)時,我們可以采用管道設(shè)計,這樣可以比較容易以插件的方式
            增加數(shù)據(jù)的處理流程。
            下面我們利用channel可被傳遞的特性來實現(xiàn)我們的管道。 為了簡化表達(dá), 我們假設(shè)在管道中
            傳遞的數(shù)據(jù)只是一個整型數(shù),在實際的應(yīng)用場景中這通常會是一個數(shù)據(jù)塊。
            首先限定基本的數(shù)據(jù)結(jié)構(gòu):
            type PipeData struct {
                value int
                handler func(int) int
                next chan int
            }
            然后我們寫一個常規(guī)的處理函數(shù)。我們只要定義一系列PipeData的數(shù)據(jù)結(jié)構(gòu)并一起傳遞給
            這個函數(shù),就可以達(dá)到流式處理數(shù)據(jù)的目的:
            func handle(queue chan *PipeData) {
            for data := range queue {
                    data.next <- data.handler(data.value)
                }
            }
            11、我們默認(rèn)創(chuàng)建的是雙向通道,單向通道沒有意義,但是我們卻可以通過強(qiáng)制轉(zhuǎn)換 將雙向通道 轉(zhuǎn)換成為單向通道 。
            var ch1 chan int  // ch1是一個正常的channel,不是單向的  
            var ch2 chan<- float64// ch2是單向channel,只用于寫float64數(shù)據(jù)
            var ch3 <-chan int // ch3是單向channel,只用于讀取int數(shù)據(jù) 
            channel是一個原生類型,因此不僅 支持被傳遞,還支持類型轉(zhuǎn)換。只有在介紹了單向channel的概念后,讀者才會明白類型轉(zhuǎn)換對于 
            channel的意義:就是在單向channel和雙向channel之間進(jìn)行轉(zhuǎn)換。
            示例如下:
            ch4 := make(chan int)
            ch5 := <-chan int(ch4) // ch5就是一個單向的讀取channel
            ch6 := chan<- int(ch4) // ch6 是一個單向的寫入channel
            基于ch4,我們通過類型轉(zhuǎn)換初始化了兩個單向channel:單向讀的ch5和單向?qū)懙腸h6。 
            從設(shè)計的角度考慮,所有的代碼應(yīng)該都遵循“最小權(quán)限原則” , 
            從而避免沒必要地使用泛濫問題, 進(jìn)而導(dǎo)致程序失控。 寫過C++程序的讀者肯定就會聯(lián)想起const 指針的用法。非const指針具備const指針的所有功能,將一個指針設(shè)定為const就是明確告訴 
            函數(shù)實現(xiàn)者不要試圖對該指針進(jìn)行修改。單向channel也是起到這樣的一種契約作用。
            下面我們來看一下單向channel的用法:
            func Parse(ch <-chan int) {
            for value := range ch {
                    fmt.Println("Parsing value", value)  
                }
            }
            除非這個函數(shù)的實現(xiàn)者無恥地使用了類型轉(zhuǎn)換,否則這個函數(shù)就不會因為各種原因而對ch 進(jìn)行寫,避免在ch中出現(xiàn)非期望的數(shù)據(jù),從而很好地實踐最小權(quán)限原則。
            12、只讀只寫 單向 channel 代碼例子    遵循權(quán)限最小化的原則
            package main
            import "fmt"
            import "time"
            //接受一個參數(shù) 是只允許讀取通道  除非直接強(qiáng)制轉(zhuǎn)換 要么你只能從channel中讀取數(shù)據(jù)
            func sCh(ch <-chan int){
               for val:= range ch {
                 fmt.Println(val)
               }
            }
            func main(){
                //創(chuàng)建一個帶100緩沖的通道 可以直接寫入 而不會導(dǎo)致 主線程堵塞
                dch:=make(chan int,100)
                for i:=0;i<100;i++{
                  dch<- i  
                }
                //傳遞進(jìn)去 只讀通道
                go sCh(dch)
                time.Sleep(1e9)
            }
            13、channel的關(guān)閉,以及判斷channel的關(guān)閉
            關(guān)閉channel非常簡單,直接使用Go語言內(nèi)置的close()函數(shù)即可:
            close(ch)
            在介紹了如何關(guān)閉channel之后,我們就多了一個問題:如何判斷一個channel是否已經(jīng)被關(guān)
            閉?我們可以在讀取的時候使用多重返回值的方式:
            x, ok := <-ch
            這個用法與map中的按鍵獲取value的過程比較類似,只需要看第二個bool返回值即可,如
            果返回值是false則表示ch已經(jīng)被關(guān)閉。
            14、Go的多核并行化編程    高性能并發(fā)編程 必須設(shè)置GOMAXPROCS 為最大核數(shù)目 這個值由runtime.NumCPU()獲取
            在執(zhí)行一些昂貴的計算任務(wù)時, 我們希望能夠盡量利用現(xiàn)代服務(wù)器普遍具備的多核特性來盡
            量將任務(wù)并行化,從而達(dá)到降低總計算時間的目的。此時我們需要了解CPU核心的數(shù)量,并針對
            性地分解計算任務(wù)到多個goroutine中去并行運(yùn)行。
            下面我們來模擬一個完全可以并行的計算任務(wù):計算N個整型數(shù)的總和。我們可以將所有整
            型數(shù)分成M份,M即CPU的個數(shù)。讓每個CPU開始計算分給它的那份計算任務(wù),最后將每個CPU
            的計算結(jié)果再做一次累加,這樣就可以得到所有N個整型數(shù)的總和:
            type Vector []float64
            // 分配給每個CPU的計算任務(wù)
            func (v Vector) DoSome(i, n int, u Vector, c chan int) {
            for ; i < n; i++ {
                     v[i] += u.Op(v[i])
                 }
                 c <- 1       
            // 發(fā)信號告訴任務(wù)管理者我已經(jīng)計算完成了
            }
            const NCPU = 16     
            // 假設(shè)總共有16核   
            func (v Vector) DoAll(u Vector) {   
                c := make(chan int, NCPU)  // 用于接收每個CPU的任務(wù)完成信號   
            for i := 0; i < NCPU; i++ {   
            go v.DoSome(i*len(v)/NCPU, (i+1)*len(v)/NCPU, u, c)
                } 
            // 等待所有CPU的任務(wù)完成
            for i := 0; i < NCPU; i++ {   
            <-c    // 獲取到一個數(shù)據(jù),表示一個CPU計算完成了
                }
            // 到這里表示所有計算已經(jīng)結(jié)束
            }
            這兩個函數(shù)看起來設(shè)計非常合理。DoAll()會根據(jù)CPU核心的數(shù)目對任務(wù)進(jìn)行分割,然后開
            辟多個goroutine來并行執(zhí)行這些計算任務(wù)。
            是否可以將總的計算時間降到接近原來的1/N呢?答案是不一定。如果掐秒表(正常點(diǎn)的話,
            應(yīng)該用7.8節(jié)中介紹的Benchmark方法) ,會發(fā)現(xiàn)總的執(zhí)行時間沒有明顯縮短。再去觀察CPU運(yùn)行
            狀態(tài), 你會發(fā)現(xiàn)盡管我們有16個CPU核心, 但在計算過程中其實只有一個CPU核心處于繁忙狀態(tài),
            這是會讓很多Go語言初學(xué)者迷惑的問題。
            官方的答案是,這是當(dāng)前版本的Go編譯器還不能很智能地去發(fā)現(xiàn)和利用多核的優(yōu)勢。雖然
            我們確實創(chuàng)建了多個goroutine,并且從運(yùn)行狀態(tài)看這些goroutine也都在并行運(yùn)行,但實際上所有
            這些goroutine都運(yùn)行在同一個CPU核心上, 在一個goroutine得到時間片執(zhí)行的時候, 其他goroutine
            都會處于等待狀態(tài)。從這一點(diǎn)可以看出,雖然goroutine簡化了我們寫并行代碼的過程,但實際上
            整體運(yùn)行效率并不真正高于單線程程序。
            在Go語言升級到默認(rèn)支持多CPU的某個版本之前,我們可以先通過設(shè)置環(huán)境變量
            GOMAXPROCS的值來控制使用多少個CPU核心。具體操作方法是通過直接設(shè)置環(huán)境變量
            GOMAXPROCS的值,或者在代碼中啟動goroutine之前先調(diào)用以下這個語句以設(shè)置使用16個CPU
            核心:
            runtime.GOMAXPROCS(16)
            到底應(yīng)該設(shè)置多少個CPU核心呢,其實runtime包中還提供了另外一個函數(shù)NumCPU()來獲
            取核心數(shù)。可以看到,Go語言其實已經(jīng)感知到所有的環(huán)境信息,下一版本中完全可以利用這些
            信息將goroutine調(diào)度到所有CPU核心上,從而最大化地利用服務(wù)器的多核計算能力。拋棄
            GOMAXPROCS只是個時間問題。 
            15、主動出讓時間片給其他 goroutine 在未來的某一時刻再來執(zhí)行當(dāng)前goroutine
            我們可以在每個goroutine中控制何時主動出讓時間片給其他goroutine,這可以使用runtime
            包中的Gosched()函數(shù)實現(xiàn)。
            實際上,如果要比較精細(xì)地控制goroutine的行為,就必須比較深入地了解Go語言開發(fā)包中
            runtime包所提供的具體功能。
            16、Go中的同步
            倡導(dǎo)用通信來共享數(shù)據(jù),而不是通過共享數(shù)據(jù)來進(jìn)行通信,但考慮
            到即使成功地用channel來作為通信手段,還是避免不了多個goroutine之間共享數(shù)據(jù)的問題,Go
            語言的設(shè)計者雖然對channel有極高的期望,但也提供了妥善的資源鎖方案。
            17、Go中的同步鎖
            倡導(dǎo)用通信來共享數(shù)據(jù),而不是通過共享數(shù)據(jù)來進(jìn)行通信,但考慮
            到即使成功地用channel來作為通信手段,還是避免不了多個goroutine之間共享數(shù)據(jù)的問題,Go
            語言的設(shè)計者雖然對channel有極高的期望,但也提供了妥善的資源鎖方案。
            對于這兩種鎖類型, 任何一個Lock()或RLock()均需要保證對應(yīng)有Unlock()或RUnlock()
            調(diào)用與之對應(yīng),否則可能導(dǎo)致等待該鎖的所有g(shù)oroutine處于饑餓狀態(tài),甚至可能導(dǎo)致死鎖。鎖的
            典型使用模式如下:
            var l sync.Mutex  
            func foo() {
            l.Lock()  
            //延遲調(diào)用 在函數(shù)退出 并且局部資源被釋放的時候 調(diào)用
            defer l.Unlock()  
            //...
            }  
            這里我們再一次見證了Go語言defer關(guān)鍵字帶來的優(yōu)雅
            18、全局唯一操作 sync.Once.Do()     sync.atomic原子操作子包
            對于從全局的角度只需要運(yùn)行一次的代碼,比如全局初始化操作,Go語言提供了一個Once
            類型來保證全局的唯一性操作,具體代碼如下:
            var a string
            var once sync.Once  
            func setup() {
            a = "hello, world"
            }  
            func doprint() {
            once.Do(setup)
            print(a)  
            }  
            func twoprint() {
            go doprint()
            go doprint()  
            }
            如果這段代碼沒有引入Once, setup()將會被每一個goroutine先調(diào)用一次, 這至少對于這個
            例子是多余的。在現(xiàn)實中,我們也經(jīng)常會遇到這樣的情況。Go語言標(biāo)準(zhǔn)庫為我們引入了Once類
            型以解決這個問題。once的Do()方法可以保證在全局范圍內(nèi)只調(diào)用指定的函數(shù)一次(這里指
            setup()函數(shù)) ,而且所有其他goroutine在調(diào)用到此語句時,將會先被阻塞,直至全局唯一的
            once.Do()調(diào)用結(jié)束后才繼續(xù)。
            這個機(jī)制比較輕巧地解決了使用其他語言時開發(fā)者不得不自行設(shè)計和實現(xiàn)這種Once效果的
            難題,也是Go語言為并發(fā)性編程做了盡量多考慮的一種體現(xiàn)。
            如果沒有once.Do(),我們很可能只能添加一個全局的bool變量,在函數(shù)setup()的最后
            一行將該bool變量設(shè)置為true。在對setup()的所有調(diào)用之前,需要先判斷該bool變量是否已
            經(jīng)被設(shè)置為true,如果該值仍然是false,則調(diào)用一次setup(),否則應(yīng)跳過該語句。實現(xiàn)代碼
            var done bool = false
            func setup() {
            a = "hello, world" 
            done = true
            }     
            func doprint() { 
            if !done {
                    setup()
                }   
            print(a)  
            }  
            這段代碼初看起來比較合理, 但是細(xì)看還是會有問題, 因為setup()并不是一個原子性操作,
            這種寫法可能導(dǎo)致setup()函數(shù)被多次調(diào)用,從而無法達(dá)到全局只執(zhí)行一次的目標(biāo)。這個問題的
            復(fù)雜性也更加體現(xiàn)了Once類型的價值。
            為了更好地控制并行中的原子性操作,sync包中還包含一個atomic子包,它提供了對于一
            些基礎(chǔ)數(shù)據(jù)類型的原子操作函數(shù),比如下面這個函數(shù):
            func CompareAndSwapUint64(val *uint64, old, new uint64) (swapped bool)
            就提供了比較和交換兩個uint64類型數(shù)據(jù)的操作。這讓開發(fā)者無需再為這樣的操作專門添加
            Lock操作。
            posted on 2017-06-02 10:58 思月行云 閱讀(323) 評論(0)  編輯 收藏 引用 所屬分類: Golang
            91久久精一区二区三区大全| 国产高潮国产高潮久久久91| 久久精品女人天堂AV麻| 日产精品久久久一区二区| 亚洲精品tv久久久久| 激情久久久久久久久久| 久久久精品午夜免费不卡| 国产精品无码久久久久久| 亚洲精品国产美女久久久| 久久久久久久精品成人热色戒| 亚洲欧美久久久久9999| 伊人色综合久久天天人守人婷| 国产精品无码久久综合网| 国内精品久久久久影院网站 | 久久久99精品成人片中文字幕| 久久精品中文字幕久久| 亚洲狠狠综合久久| 久久国产综合精品五月天| 777久久精品一区二区三区无码| 久久美女网站免费| 国产一区二区精品久久凹凸| 看全色黄大色大片免费久久久| 久久涩综合| 无码人妻久久一区二区三区免费丨 | 久久久精品免费国产四虎| 亚洲国产成人久久综合一 | 狠狠色丁香婷婷久久综合五月 | 国产精品久久网| 久久精品成人免费国产片小草| 久久免费视频6| 亚洲伊人久久精品影院| 99精品久久精品| 香蕉aa三级久久毛片| 无码人妻久久一区二区三区免费| 久久香蕉一级毛片| 性高湖久久久久久久久AAAAA| 久久亚洲AV无码精品色午夜麻豆| 久久久久人妻一区精品性色av| AA级片免费看视频久久| 久久人人爽人人爽人人片AV不 | 久久综合亚洲鲁鲁五月天|