中文字幕理论片,69视频免费在线观看,亚洲成人app,国产1级毛片,刘涛最大尺度戏视频,欧美亚洲美女视频,2021韩国美女仙女屋vip视频

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
一看就懂系列之Golang的goroutine和通道

https://blog.csdn.net/u011957758/article/details/81159481

前言

如果說php是最好的語言,那么golang就是最并發(fā)的語言。
支持golang的并發(fā)很重要的一個是goroutine的實現(xiàn),那么本文將重點圍繞goroutine來做一下相關(guān)的筆記,以便日后快速留戀。

10s后,以下知識點即將靠近:

1.從并發(fā)模型說起
2.goroutine的簡介
3.goroutine的使用姿勢
4.通道(channel)的簡介
5.重要的四種通道使用
6.goroutine死鎖與處理
7.select的簡介
8.select的應(yīng)用場景
9.select死鎖

正文

1.從并發(fā)模型說起

看過很多大神簡介,各種研究高并發(fā),那么就通俗的說下并發(fā)。
并發(fā)目前來看比較主流的就三種:

1.多線程

每個線程一次處理一個請求,線程越多可并發(fā)處理的請求數(shù)就越多,但是在高并發(fā)下,多線程開銷會比較大。

2.協(xié)程

無需搶占式的調(diào)度,開銷小,可以有效的提高線程的并發(fā)性,從而避免了線程的缺點的部分

3.基于異步回調(diào)的IO模型

說一個熟悉的,比如nginx使用的就是epoll模型,通過事件驅(qū)動的方式與異步IO回調(diào),使得服務(wù)器持續(xù)運轉(zhuǎn),來支撐高并發(fā)的請求


為了追求更高效和低開銷的并發(fā),golang的goroutine來了。

2.goroutine的簡介

定義:在go里面,每一個并發(fā)執(zhí)行的活動成為goroutine。

詳解:goroutine可以認為是輕量級的線程,與創(chuàng)建線程相比,創(chuàng)建成本和開銷都很小,每個goroutine的堆棧只有幾kb,并且堆棧可根據(jù)程序的需要增長和縮小(線程的堆棧需指明和固定),所以go程序從語言層面支持了高并發(fā)。

程序執(zhí)行的背后:當一個程序啟動的時候,只有一個goroutine來調(diào)用main函數(shù),稱它為主goroutine,新的goroutine通過go語句進行創(chuàng)建。

3.goroutine的使用姿勢

3.1單個goroutine創(chuàng)建

在函數(shù)或者方法前面加上關(guān)鍵字go,即創(chuàng)建一個并發(fā)運行的新goroutine。

上代碼:

package main import ( 'fmt' 'time' ) func HelloWorld() { fmt.Println('Hello world goroutine') } func main() { go HelloWorld() // 開啟一個新的并發(fā)運行 time.Sleep(1*time.Second) fmt.Println('我后面才輸出來') }
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

以上執(zhí)行后會輸出:

Hello world goroutine
我后面才輸出來1212

需要注意的是,執(zhí)行速度很快,一定要加sleep,不然你一定可以看到goroutine里頭的輸出。

這也說明了一個關(guān)鍵點:當main函數(shù)返回時,所有的gourutine都是暴力終結(jié)的,然后程序退出。

3.2多個goroutine創(chuàng)建

package main import ( 'fmt' 'time' ) func DelayPrint() { for i := 1; i <= 4; i++ { time.Sleep(250 * time.Millisecond) fmt.Println(i) } } func HelloWorld() { fmt.Println('Hello world goroutine') } func main() { go DelayPrint() // 開啟第一個goroutine go HelloWorld() // 開啟第二個goroutine time.Sleep(2*time.Second) fmt.Println('main function') }
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

  • 18

  • 19

  • 20

  • 21

  • 22

  • 23

  • 24

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

  • 18

  • 19

  • 20

  • 21

  • 22

  • 23

  • 24

函數(shù)輸出:

Hello world goroutine
1
2
3
4
5
main function12345671234567

有心的同學(xué)可能會發(fā)現(xiàn),DelayPrint里頭有sleep,那么會導(dǎo)致第二個goroutine堵塞或者等待嗎?
答案是:no
疑惑:當程序執(zhí)行g(shù)o FUNC()的時候,只是簡單的調(diào)用然后就立即返回了,并不關(guān)心函數(shù)里頭發(fā)生的故事情節(jié),所以不同的goroutine直接不影響,main會繼續(xù)按順序執(zhí)行語句。

4.通道(channel)的簡介

4.1簡介

如果說goroutine是Go并發(fā)的執(zhí)行體,那么'通道'就是他們之間的連接。
通道可以讓一個goroutine發(fā)送特定的值到另外一個goroutine的通信機制。

4.2聲明&傳值&關(guān)閉

聲明

var ch chan int // 聲明一個傳遞int類型的channel ch := make(chan int) // 使用內(nèi)置函數(shù)make()定義一個channel //========= ch <- value // 將一個數(shù)據(jù)value寫入至channel,這會導(dǎo)致阻塞,直到有其他goroutine從這個channel中讀取數(shù)據(jù) value := <-ch // 從channel中讀取數(shù)據(jù),如果channel之前沒有寫入數(shù)據(jù),也會導(dǎo)致阻塞,直到channel中被寫入數(shù)據(jù)為止 //========= close(ch) // 關(guān)閉channel
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

有沒注意到關(guān)鍵字'阻塞'?,這個其實是默認的channel的接收和發(fā)送,其實也有非阻塞的,請看下文。

5.重要的四種通道使用

1.無緩沖通道

說明:無緩沖通道上的發(fā)送操作將會被阻塞,直到另一個goroutine在對應(yīng)的通道上執(zhí)行接收操作,此時值才傳送完成,兩個goroutine都繼續(xù)執(zhí)行。

上代碼:

package main

import (
'fmt'
'time'
)
var done chan bool
func HelloWorld() {
fmt.Println('Hello world goroutine')
time.Sleep(1*time.Second)
done <- true
}
func main() {
done = make(chan bool)  // 創(chuàng)建一個channel
go HelloWorld()
<-done
}12345678910111213141516171234567891011121314151617

輸出:

Hello world goroutine
  • 1

  • 1

由于main不會等goroutine執(zhí)行結(jié)束才返回,前文專門加了sleep輸出為了可以看到goroutine的輸出內(nèi)容,那么在這里由于是阻塞的,所以無需sleep。

(小嘗試:可以將代碼中'done <- true'和'<-done',去掉再執(zhí)行,看看會發(fā)生啥?)

2.管道

通道可以用來連接goroutine,這樣一個的輸出是另一個輸入。這就叫做管道。

例子:

package main

import (
'fmt'
'time'
)
var echo chan string
var receive chan string

// 定義goroutine 1 
func Echo() {
time.Sleep(1*time.Second)
echo <- '咖啡色的羊駝'
}

// 定義goroutine 2
func Receive() {
temp := <- echo // 阻塞等待echo的通道的返回
receive <- temp
}


func main() {
echo = make(chan string)
receive = make(chan string)

go Echo()
go Receive()

getStr := <-receive   // 接收goroutine 2的返回

fmt.Println(getStr)
}123456789101112131415161718192021222324252627282930313233123456789101112131415161718192021222324252627282930313233

在這里不一定要去關(guān)閉channel,因為底層的垃圾回收機制會根據(jù)它是否可以訪問來決定是否自動回收它。(這里不是根據(jù)channel是否關(guān)閉來決定的)

3.單向通道類型

當程序則夠復(fù)雜的時候,為了代碼可讀性更高,拆分成一個一個的小函數(shù)是需要的。

此時go提供了單向通道的類型,來實現(xiàn)函數(shù)之間channel的傳遞。

上代碼:

package main import ( 'fmt' 'time' ) // 定義goroutine 1 func Echo(out chan<- string) { // 定義輸出通道類型 time.Sleep(1*time.Second) out <- '咖啡色的羊駝' close(out) } // 定義goroutine 2 func Receive(out chan<- string, in <-chan string) { // 定義輸出通道類型和輸入類型 temp := <-in // 阻塞等待echo的通道的返回 out <- temp close(out) } func main() { echo := make(chan string) receive := make(chan string) go Echo(echo) go Receive(receive, echo) getStr := <-receive // 接收goroutine 2的返回 fmt.Println(getStr) }
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

  • 18

  • 19

  • 20

  • 21

  • 22

  • 23

  • 24

  • 25

  • 26

  • 27

  • 28

  • 29

  • 30

  • 31

  • 32

  • 33

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

  • 18

  • 19

  • 20

  • 21

  • 22

  • 23

  • 24

  • 25

  • 26

  • 27

  • 28

  • 29

  • 30

  • 31

  • 32

  • 33

程序輸出:

咖啡色的羊駝11

4.緩沖管道

goroutine的通道默認是是阻塞的,那么有什么辦法可以緩解阻塞?
答案是:加一個緩沖區(qū)。

對于go來說創(chuàng)建一個緩沖通道很簡單:

ch := make(chan string, 3) // 創(chuàng)建了緩沖區(qū)為3的通道 //========= len(ch) // 長度計算 cap(ch) // 容量計算
  • 1

  • 2

  • 3

  • 4

  • 5

  • 1

  • 2

  • 3

  • 4

  • 5

6.goroutine死鎖與友好退出

6.1goroutine死鎖

來一個死鎖現(xiàn)場一:

package main

func main() {
ch := make(chan int)
<- ch // 阻塞main goroutine, 通道被鎖
}123456123456

輸出:

fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan receive]: main.main()
  • 1

  • 2

  • 3

  • 4

  • 1

  • 2

  • 3

  • 4

死鎖現(xiàn)場2:

package main

func main() {
cha, chb := make(chan int), make(chan int)

go func() {
cha <- 1 // cha通道的數(shù)據(jù)沒有被其他goroutine讀取走,堵塞當前goroutine
chb <- 0
}()

<- chb // chb 等待數(shù)據(jù)的寫
}123456789101112123456789101112

為什么會有死鎖的產(chǎn)生?

非緩沖通道上如果發(fā)生了流入無流出,或者流出無流入,就會引起死鎖。
或者這么說:goroutine的非緩沖通道里頭一定要一進一出,成對出現(xiàn)才行。
上面例子屬于:一:流出無流入;二:流入無流出

當然,有一個例外:

func main() { ch := make(chan int) go func() { ch <- 1 }() }
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

執(zhí)行以上代碼將會發(fā)現(xiàn),竟然沒有報錯。
what?
不是說好的一進一出就死鎖嗎?
仔細研究會發(fā)現(xiàn),其實根本沒等goroutine執(zhí)行完,main函數(shù)自己先跑完了,所以就沒有數(shù)據(jù)流入主的goroutine,就不會被阻塞和報錯

6.2goroutine的死鎖處理

有兩種辦法可以解決:

1.把沒取走的取走便是

package main

func main() {
cha, chb := make(chan int), make(chan int)

go func() {
cha <- 1 // cha通道的數(shù)據(jù)沒有被其他goroutine讀取走,堵塞當前goroutine
chb <- 0
}()

<- cha // 取走便是
<- chb // chb 等待數(shù)據(jù)的寫
}1234567891011121312345678910111213

2.創(chuàng)建緩沖通道

package main func main() { cha, chb := make(chan int, 3), make(chan int) go func() { cha <- 1 // cha通道的數(shù)據(jù)沒有被其他goroutine讀取走,堵塞當前goroutine chb <- 0 }() <- chb // chb 等待數(shù)據(jù)的寫 }
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

這樣的話,cha可以緩存一個數(shù)據(jù),cha就不會掛起當前的goroutine了。除非再放兩個進去,塞滿緩沖通道就會了。

7.select的簡介

定義:在golang里頭select的功能與epoll(nginx)/poll/select的功能類似,都是堅挺IO操作,當IO操作發(fā)生的時候,觸發(fā)相應(yīng)的動作。

select有幾個重要的點要強調(diào):

1.如果有多個case都可以運行,select會隨機公平地選出一個執(zhí)行,其他不會執(zhí)行
上代碼:

package main

import 'fmt'

func main() {
ch := make (chan int, 1)

ch<-1
select {
case <-ch:
fmt.Println('咖啡色的羊駝')
case <-ch:
fmt.Println('黃色的羊駝')
}
}123456789101112131415123456789101112131415

輸出:

(隨機)二者其一
  • 1

  • 1

2.case后面必須是channel操作,否則報錯。

上代碼:

package main

import 'fmt'

func main() {
ch := make (chan int, 1)
ch<-1
select {
case <-ch:
fmt.Println('咖啡色的羊駝')
case 2:
fmt.Println('黃色的羊駝')
}
}12345678910111213141234567891011121314

輸出報錯:

2 evaluated but not used select case must be receive, send or assign recv
  • 1

  • 2

  • 1

  • 2

3.select中的default子句總是可運行的。所以沒有default的select才會阻塞等待事件
上代碼:

package main

import 'fmt'

func main() {
ch := make (chan int, 1)
// ch<-1   <= 注意這里備注了。
select {
case <-ch:
fmt.Println('咖啡色的羊駝')
default:
fmt.Println('黃色的羊駝')
}
}12345678910111213141234567891011121314

輸出:

黃色的羊駝
  • 1

  • 1

4.沒有運行的case,那么江湖阻塞事件發(fā)生報錯(死鎖)

package main

import 'fmt'

func main() {
ch := make (chan int, 1)
// ch<-1   <= 注意這里備注了。
select {
case <-ch:
fmt.Println('咖啡色的羊駝')
}
}123456789101112123456789101112

輸出報錯:

fatal error: all goroutines are asleep - deadlock!
  • 1

  • 1

8.select的應(yīng)用場景

1.timeout 機制(超時判斷)

package main

import (
'fmt'
'time'
)

func main() {
timeout := make (chan bool, 1)
go func() {
time.Sleep(1*time.Second) // 休眠1s,如果超過1s還沒I操作則認為超時,通知select已經(jīng)超時啦~
timeout <- true
}()
ch := make (chan int)
select {
case <- ch:
case <- timeout:
fmt.Println('超時啦!')
}
}12345678910111213141516171819201234567891011121314151617181920

以上是入門版,通常代碼中是這么寫的:

package main import ( 'fmt' 'time' ) func main() { ch := make (chan int) select { case <-ch: case <-time.After(time.Second * 1): // 利用time來實現(xiàn),After代表多少時間后執(zhí)行輸出東西 fmt.Println('超時啦!') } }
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

2.判斷channel是否阻塞(或者說channel是否已經(jīng)滿了)

package main

import (
'fmt'
)

func main() {
ch := make (chan int, 1)  // 注意這里給的容量是1
ch <- 1
select {
case ch <- 2:
default:
fmt.Println('通道channel已經(jīng)滿啦,塞不下東西了!')
}
}123456789101112131415123456789101112131415

3.退出機制

package main import ( 'fmt' 'time' ) func main() { i := 0 ch := make(chan string, 0) defer func() { close(ch) }() go func() { DONE: for { time.Sleep(1*time.Second) fmt.Println(time.Now().Unix()) i++ select { case m := <-ch: println(m) break DONE // 跳出 select 和 for 循環(huán) default: } } }() time.Sleep(time.Second * 4) ch<-'stop' }
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

  • 18

  • 19

  • 20

  • 21

  • 22

  • 23

  • 24

  • 25

  • 26

  • 27

  • 28

  • 29

  • 30

  • 31

  • 32

  • 33

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

  • 18

  • 19

  • 20

  • 21

  • 22

  • 23

  • 24

  • 25

  • 26

  • 27

  • 28

  • 29

  • 30

  • 31

  • 32

  • 33

輸出:

1532390471
1532390472
1532390473
stop
15323904741234512345

這邊要強調(diào)一點:退出循環(huán)一定要用break + 具體的標記,或者goto也可以。否則其實不是真的退出。

package main import ( 'fmt' 'time' ) func main() { i := 0 ch := make(chan string, 0) defer func() { close(ch) }() go func() { for { time.Sleep(1*time.Second) fmt.Println(time.Now().Unix()) i++ select { case m := <-ch: println(m) goto DONE // 跳出 select 和 for 循環(huán) default: } } DONE: }() time.Sleep(time.Second * 4) ch<-'stop' }
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

  • 18

  • 19

  • 20

  • 21

  • 22

  • 23

  • 24

  • 25

  • 26

  • 27

  • 28

  • 29

  • 30

  • 31

  • 32

  • 33

  • 34

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

  • 18

  • 19

  • 20

  • 21

  • 22

  • 23

  • 24

  • 25

  • 26

  • 27

  • 28

  • 29

  • 30

  • 31

  • 32

  • 33

  • 34

輸出:

1532390525
1532390526
1532390527
1532390528
stop1234512345

9.select死鎖

select不注意也會發(fā)生死鎖,前文有提到一個,這里分幾種情況,重點再次強調(diào):

1.如果沒有數(shù)據(jù)需要發(fā)送,select中又存在接收通道數(shù)據(jù)的語句,那么將發(fā)送死鎖

package main func main() { ch := make(chan string) select { case <-ch: } }
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

預(yù)防的話加default。

空select,也會引起死鎖

package main

func main() {  
    select {}
}1234512345
本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Go 語言系列27:Select
Golang Channel用法簡編 | Tony Bai
go語言學(xué)習(xí)筆記 | Golang中文社區(qū)(Go語言構(gòu)建) | Go語言中文網(wǎng) | Go語言學(xué)習(xí)園地
回答我,停止 Goroutine 有幾種方法?
一篇文章帶你了解Go語言基礎(chǔ)之并發(fā)(channel)
Golang學(xué)習(xí)筆記
更多類似文章 >>
生活服務(wù)
熱點新聞
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服