<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                [toc] # 1. 進程和線程介紹 ## 1.1 進程和線程的介紹 ### 1.1.1 進程 進程是程序在操作系統中的一次執行過程,是系統進行資源分配和調度的基本單位,每個進程都有自己的獨立內存空間,不同進程通過進程間通信來通信。由于進程比較重量,占據獨立的內存,所以上下文進程間的切換開銷(棧、寄存器、虛擬內存、文件句柄等)比較大,但相對比較穩定安全 ### 1.1.2 線程 線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位.線程自己基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源。線程間通信主要通過共享內存,上下文切換很快,資源開銷較少,但相比進程不夠穩定容易丟失數據。 ### 1.1.3 協程 協程是一種用戶態的輕量級線程,協程的調度完全由用戶控制。協程擁有自己的寄存器上下文和棧。協程調度切換時,將寄存器上下文和棧保存到其他地方,在切回來的時候,恢復先前保存的寄存器上下文和棧,直接操作棧則基本沒有內核切換的開銷,可以不加鎖的訪問全局變量,所以上下文的切換非常快。 ### 1.1.4 進程和線程的關系 - 一個進程可以創建和銷毀多個線程,同一個進程中的多個線程可以并發執行 - 一個程序至少有一個進程,一個進程至少有一個線程 ## 1.2 區別 ### 1.2.1 進程多與線程比較 - <b>地址空間</b>:線程是進程內的一個執行單元,進程內至少有一個線程,它們共享進程的地址空間,而進程有自己獨立的地址空間 - <b>資源擁有</b>:進程是資源分配和擁有的單位,同一個進程內的線程共享進程的資源 - <b>基本單位</b>: 線程是CPU調度的基本單位,進程是系統進行資源分配和調度的基本單位 - <b>并發執行</b>: 二者均可并發執行 - <b>層級關系</b>:一個進程可以創建和銷毀多個線程,同一個進程中的多個線程可以并發執行 - <b>執行過程</b>:線程在執行過程中與進程還是有區別的。每個獨立的線程有一個程序運行的入口、順序執行序列和程序的出口。`但是線程不能夠獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。` <b> 為了加深理解,做個簡單的比喻:進程=火車,線程=車廂 </b> 1. 線程在進程下行進(單純的車廂無法運行) 2. 一個進程可以包含多個線程(一輛火車可以有多個車廂) 3. 不同進程間數據很難共享(一輛火車上的乘客很難換到另外一輛火車,比如站點換乘) 4. 同一進程下不同線程間數據容易共享(A車廂換到B車廂很容易) 5. 進程要比線程消耗更多的計算機資源(采用多列火車相比多個車廂更耗資源) 6. 進程間不會相互影響,一個線程掛掉將導致整個進程掛掉(一列火車不會影響到另外一列火車,但是如果一列火車上中間的一節車廂與前一節產生斷裂,將影響后面的所有車廂) 7. 進程可以拓展到多機,進程最適合多核(不同火車可以開在多個軌道上,同一火車的車廂不能在行進的不同的軌道上) 8. 進程使用的內存地址可以上鎖,即一個線程使用某些共享內存時,其他線程必須等它結束,才能使用這一塊內存。(比如火車上的洗手間)-"互斥鎖" 9. 進程使用的內存地址可以限定使用量(比如火車上的餐廳,最多只允許多少人進入,如果滿了需要在門口等,等有人出來了才能進去)-"信號量" ### 1.2.2 協程多與線程進行比較 - 一個線程可以多個協程,一個進程也可以單獨擁有多個協程,這樣python中則能使用多核CPU。 - 線程進程都是同步機制,而協程則是異步 - 協程能保留上一次調用時的狀態,每次過程重入時,就相當于進入上一次調用的狀態 ### 1.2.3 其他介紹資料 - [進程與線程的一個簡單解釋(阮一峰)](http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html) - [漫畫:什么是協程?](https://www.itcodemonkey.com/article/4620.html) # 2. 并發和并行 ## 2.1 并發 多線程程序在單核上運行,就是并發 ### 2.1.1 并發的特點 - 多個任務作用在一個cpu - 從微觀角度看,在一個時間點上,只有一個任務在執行 ## 2.2 并行 多線程程序在多核上運行,就是并行 ### 2.2.1 并行的特點 - 多個任務作用在多個cpu - 從微觀角度看,在一個時間點上,有多個任務在執行 # 3. Go 協程和 Go 主線程 ## 3.1 關系介紹 - 主線程是一個物理線程,直接作用在 cpu 上的。是重量級的,非常耗費 cpu 資源。 - 協程從主線程開啟的,是輕量級的線程,是邏輯態。對資源消耗相對小。 - Golang的協程機制是重要的特點,可以輕松的開啟上萬個協程。其它編程語言的并發機制是一 般基于線程的,開啟過多的線程,資源耗費大,這里就突顯 Golang 在并發上的優勢了 ## 3.2 Go主進程和Go協程(goroutine)關系示意圖 ![](https://mrliuqh.github.io/directionsImg/go/go%E4%B8%BB%E7%BA%BF%E7%A8%8B%E5%92%8C%E5%8D%8F%E7%A8%8B.png) ## 3.3 Go協程的特點 - 有獨立的棧空間 - 共享程序堆空間 - 調度由用戶控制 - 協程是輕量級的線程 # 4. Go協程(Goroutine)的使用 Go程序中使用go關鍵字為一個函數創建一個goroutine。一個函數可以被創建多個goroutine,一個goroutine必定對應一個函數。 ## 4.1 使用方式 ### 4.1.1 普通函數創建goroutine - 使用格式 ``` go 函數名 ( 參數列表 ) ``` - 函數名:要調用的函數名。 - 參數列表:調用函數需要傳入的參數。 ><font color=red>使用go關鍵字創建goroutine時,被調用函數的返回值會被忽略。</font>如果需要在goroutine中返回數據,則需要通過通道(channel)把數據從goroutine中作為返回值傳出。 - 使用示例 ``` package main import ( "fmt" "time" "strconv" ) func test() { var i =1 for { fmt.Println(""+strconv.Itoa(i)) time.Sleep(time.Second) i++ } } func main() { // 為一個普通函數創建goroutine go test() //接收用戶輸入,知道按Enter鍵時 var input string //將用戶輸入內容寫入input變量中,并返回,整個程序終止 fmt.Scanln(&input) } ``` 輸出: ``` 1 2 3 4 5 6 ... exit ``` > <font color=red>所有goroutine在mian()函數結束時會一同結束</font> ### 4.1.2 匿名函數創建goroutine `go關鍵字后也可以為匿名函數或閉包啟動goroutine` - 使用格式 `使用匿名函數或閉包創建goroutine時,除了將函數定義部分寫在go的后面之外,還需要加上匿名函數的調用參數` ``` go func( 參數列表 ){ 函數體 }( 調用參數列表 ) ``` - 參數列表:函數體內的參數變量列表。 - 函數體:匿名函數的代碼。 - 調用參數列表:啟動goroutine時,需要向匿名函數傳遞的調用參數 - 使用示例 ``` package main import ( "fmt" "time" ) func main() { go func() { var i int for { fmt.Println("計時器:", i) time.Sleep(time.Second) i++ } }() var input string fmt.Scanln(&input) } ``` ## 4.2 并發運行性能調整 ### 4.2.1 設置運行的 cpu 數 **為了充分了利用多 cpu 的優勢,在 Golang 程序中,可以通過runtime.GOMAXPROCS() 函數 設置運行的 cpu 數目** - 使用格式 ``` runtime.GOMAXPROCS( 邏輯CPU) //邏輯CPU可以通過 runtime.NumCpu()函數獲取 ``` 這里的邏輯CPU數量可以有如下幾種數值: - 邏輯CPU < 1:不修改任何數值 - 邏輯CPU = 1:單核心執行 - 邏輯CPU > 1:多核并發執行 > - Go 1.5版本之前,默認使用的是單核心執行。從Go 1.5版本開始,默認執行上面語句以便讓代碼并發執行,最大效率地利用CPU。 > - GOMAXPROCS同時也是一個環境變量,在應用程序啟動前設置環境變量也可以起到相同的作用。 # 5. 通道(Channel) ## 5.1 為什么需要 channel? `單純地將函數并發執行是沒有意義的。函數與函數間需要交換數據才能體現并發執行函數的意義。雖然可以使用共享內存進行數據交換,但是共享內存在不同的goroutine中容易發生競態問題。為了保證數據交換的正確性,必須使用互斥量對內存進行加鎖,這種做法勢必造成性能問題。Go語言提倡使用通信的方法代替共享內存,這里通信的方法就是使用通道` ### 5.1.1 goroutine與channel的通信圖 ![](https://mrliuqh.github.io/directionsImg/go/goroutine%E4%B8%8Echannel%E7%9A%84%E9%80%9A%E4%BF%A1.png) ## 5.2 通道的特性 - 通道是一種特殊的類型 - 任何時候,同時只能有一個goroutine訪問通道進行發送和獲取數據 - channle本質就是一個數據結構-隊列 ![](https://mrliuqh.github.io/directionsImg/go/gochannel.png) - goroutine間通過通道就可以通信 - 通道像一個傳送帶或者隊列,總是遵循先入先出(First In First Out)的規則,保證收發數據的順序 - channel 是線程安全,多個協程操作同一個channel時,不會發生資源競爭問題(競態) ## 5.3 創建通道 ### 5.3.1 創建無緩沖通道 `通道是引用類型,需要使用make進行創建` ``` 通道實例 := make(chan數據類型) ``` - 數據類型:通道內傳輸的元素類型。 - 通道實例:通過make創建的通道句柄。 - 使用示例 ``` // 創建一個整型類型的通道 intch := make(chan int) // 創建一個空接口類型的通道,可以存放任意格式 interfacech := make(chan interface{}) // 創建Mystruct指針類型的通道,可以存放*Equip type Mystruct struct{ /* 一些字段 */ ... } structch := make(chan *Mystruct) ``` ### 5.3.2 創建有緩沖通道 `在無緩沖通道的基礎上,為通道增加一個有限大小的存儲空間形成帶緩沖通道。帶緩沖通道在發送時無需等待接收方接收即可完成發送過程,并且不會發生阻塞,只有當存儲空間滿時才會發生阻塞。同理,如果緩沖通道中有數據,接收時將不會發生阻塞,直到通道中沒有數據可讀時,通道將會再度阻塞。` - 聲明格式 ``` 通道實例 := make(chan通道類型, 緩沖大小) ``` - 通道類型:和無緩沖通道用法一致,影響通道發送和接收的數據類型。 - 緩沖大小:決定通道最多可以保存的元素數量。 - 通道實例:被創建出的通道實例。 - 使用示例 ``` func main() { //創建可以存放 3 map 類型通道 intCh := make(chan int, 3) //數據發送到通道中 intCh <- 34 intCh <- 20 intCh <- 10 /* 注意: 當我們給管寫入數據時,不能超過其容量, 否則報錯:fatal error: all goroutines are asleep - deadlock! */ //intCh <- 1 } ``` ### 5.3.3 帶緩沖通道阻塞條件 `帶緩沖通道在很多特性上和無緩沖通道是類似的。無緩沖通道可以看作是長度永遠為0的帶緩沖通道。因此根據這個特性,帶緩沖通道在下面列舉的情況下依然會發生阻塞` - 帶緩沖通道被填滿時,嘗試再次發送數據時發生阻塞。 - 帶緩沖通道為空時,嘗試接收數據時發生阻塞。 ### 5.3.4 為什么對通道要限制長度? >我們知道通道(channel)是在兩個goroutine間通信的橋梁。使用goroutine的代碼必然有一方提供數據,一方消費數據。當提供數據一方的數據供給速度大于消費方的數據處理速度時,如果通道不限制長度,那么內存將不斷膨脹直到應用崩潰。因此,限制通道的長度有利于約束數據提供方的供給速度,供給數據量必須在消費方處理量+通道長度的范圍內,才能正常地處理數據。 ### 5.3.5 單向通道聲明 `只能發送的通道類型為: chan <- x,只能接收的通道類型為: x <- chan` ``` ch := make(chan int) // 聲明一個只能發送的通道類型,并賦值為ch var ch Send Only chan<- int = ch //聲明一個只能接收的通道類型,并賦值為ch var ch Recv Only <-chan int = ch ``` ## 5.4 發送數據 `通道創建后,就可以使用特殊的操作符“<-”,向通道進行發送或者從通道接收數據。` ### 5.4.1 使用方法 ``` func main() { //創建可以存放 3 map 類型通道 intCh := make(chan int, 3) //數據發送到通道中 intCh <- 34 intCh <- 20 intCh <- 10 /* 注意: 當我們給管寫入數據時,不能超過其容量, 否則報錯:fatal error: all goroutines are asleep - deadlock! */ //intCh <- 1 } ``` ## 5.5 接收數據 ### 5.5.1 阻塞接收數據 `阻塞模式接收數據時,將接收變量作為“<-”操作符的左值,格式如下:` ``` data := <-ch ``` > 執行該語句時將會阻塞,直到接收到數據并賦值給data變量 ### 5.5.2 非阻塞接收數據 `使用非阻塞方式從通道接收數據時,語句不會發生阻塞,格式如下:` ``` data, ok := <-ch ``` - data:表示接收到的數據。未接收到數據時,data為通道類型的零值。 - ok:表示是否接收到數據。 >非阻塞的通道接收方法可能造成高的CPU占用,因此使用非常少。如果需要實現接收超時檢測,可以配合select和計時器channel進行。 ### 5.5.3 接收任意數據,忽略接收的數據 `阻塞接收數據后,忽略從通道返回的數據,格式如下:` ``` <-ch ``` > 執行該語句時將會發生阻塞,直到接收到數據,但接收到的數據會被忽略。<font color=ffb800>這個方式實際上只是通過通道在goroutine間阻塞收發實現并發同步。</font> - 使用示例 ``` func main() { intCh := make(chan int) start := time.Now() testNum := 10 go func() { fmt.Println("start goroutine....") for i := 0; i < 3; i++ { testNum += i time.Sleep(time.Second) } //數據寫入通道 intCh <- testNum fmt.Println("end goroutine....") }() //等待匿名函數執行完成 fmt.Println("wait goroutine...") //執行該語句時將會發生阻塞,直到接收到數據,但接收到的數據會被忽略 <-intCh diff := time.Now().Sub(start) fmt.Printf("testNum: %d\n", testNum) fmt.Println("耗時: ", diff) } ``` ## 5.6 channel 使用的注意事項 - channle的容量放滿后,就不能再放入了 - channel中只能存放指定的數據類型 - 空接口類型的通道能接收任意參數 ``` interfaceCh := make(chan interface{}, 4) //發送字符串 interfaceCh <- "hello" //發送整型 interfaceCh <- 100 //發送切片 interfaceCh <- []int{1, 2, 3, 4} /* range函數遍歷每個從通道接收到的數據,因為queue再發送完3個數據之后就關閉了通道,所以這里我們range函數在接收到3個數據之后就結束了。 如果上面的queue通道不關閉,那么range函數就不會結束,從而在接收第4個數據的時候就阻塞了。 */ close(interfaceCh) for data := range interfaceCh { fmt.Println(data) } ``` - 發送將持續阻塞直到數據被接收 - 通過range函數遍歷通道接收數據時,要再發送完數據到通道后,用Close()函數顯示的關閉通道,否則range函數就不會結束 - 通道一次只能接收一個數據元素 ## 5.7 使用select多路復用 `在使用通道時,想同時接收多個通道的數據是一件困難的事情。通道在接收數據時,如果沒有數據可以接收將會發生阻塞。` 雖然可以使用如下模式進行遍歷,但運行性能會非常差。 ``` //運行性能會非常差 for{ // 嘗試接收ch1通道 data, ok := <-ch1 // 嘗試接收ch2通道 data, ok := <-ch2 // 接收后續通道 … } ``` `Go語言中提供了select關鍵字,可以同時響應多個通道的操作。select的每個case都會對應一個通道的收發過程。當收發完成時,就會觸發case中響應的語句。多個操作在每次select中挑選一個進行響應。` ### 5.7.1 聲明格式 格式如下: ``` select{ case 操作1: //響應操作1 case 操作2: //響應操作2 … default: //沒有操作情況 } ``` - case <- ch: 代表接收任意數據 - case d:= <-ch: 接收變量 - case ch <- 120: 發送數據到通道 ### 5.7.2 使用示例 ``` //生成通道int ch intCh := make(chan int, 10) for i := 1; i < 10; i++ { intCh <- i } //生成通道string ch strCh := make(chan string, 10) for i := 1; i < 10; i++ { strCh <- "String:" + fmt.Sprintf("%d", i) } /* 傳統的方法在遍歷管道時,如果不關閉會阻塞,從而導致deadlock,在實際開發中,可能我們不好確定什么關閉該管道, 可以使用 select 方式可以解決 */ //使用select for { select { case v := <-intCh: fmt.Println("intCH: ", v) case v := <-strCh: fmt.Println("strCh: ", v) default: fmt.Println("Noting!") return } } ```
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看