我就廢話不多說(shuō)了,大家還是直接看代碼吧~
package main
import "fmt"
import "sync"
var ch = make(chan int)
func do(lock *sync.Mutex, ct *int) {
lock.Lock()
*ct++
lock.Unlock()
ch - 1
}
func main() {
fmt.Println("hello thread")
var ct = 0
lock := sync.Mutex{}
for i:=0; i10; i++ {
go do(lock, ct)
}
for i:=0; i10; i++ {
- ch
}
fmt.Println("ct=", ct)
}
輸出: 10
補(bǔ)充:Goroutine協(xié)程之間的數(shù)據(jù)溝通的方式
一個(gè)服務(wù)器物理線程能夠跑多個(gè)goroutine,成千上萬(wàn)個(gè)goroutine 實(shí)際上跑在物理線程上的也就幾十個(gè),但是java和c++創(chuàng)建成千上萬(wàn)個(gè)線程會(huì)使得系統(tǒng)反應(yīng)更慢,這是為什么goroutine能很快的原因。
那么goroutine協(xié)程之間是如何進(jìn)行通信的呢?有兩種方式,
第一使用全局變量和鎖同步:讀寫(xiě)鎖或互斥鎖對(duì)全局變量進(jìn)行加鎖,實(shí)現(xiàn)多個(gè)goroute的數(shù)據(jù)共享。
第二:Channel 管道進(jìn)行數(shù)據(jù)同步
1.加鎖操作
互斥鎖就是將公共資源進(jìn)行加鎖操作,以便于goroute對(duì)數(shù)據(jù)進(jìn)行更改。
package main
import (
"fmt"
lock "sync"
"time"
)
type task struct {
n int
}
//通過(guò)全局的 map 來(lái)通訊
var (
sum
)
func calc(t *task) {
var sum uint64
sum = 1
for i := 1; i t.n; i++ {
sum *= uint64(i)
}
fmt.Printf("%d! = %v\n", t.n, sum)
lock.Lock()
sum++
lock.Unlock()
}
func main() {
for i := 0; i 100; i++ {
var t *task = task{n: i}
go calc(t)
}
time.Sleep(5 * time.Second)
lock.Lock()
// for k, v := range m {
// fmt.Printf("%d! = %v\n", k, v)
// }
lock.Unlock()
}
2.channel管道通信
單純地將函數(shù)并發(fā)執(zhí)行是沒(méi)有意義的。函數(shù)與函數(shù)間需要交換數(shù)據(jù)才能體現(xiàn)并發(fā)執(zhí)行函數(shù)的意義。雖然可以使用共享內(nèi)存進(jìn)行數(shù)據(jù)交換,但是共享內(nèi)存在不同的 goroutine 中容易發(fā)生競(jìng)態(tài)問(wèn)題。為了保證數(shù)據(jù)交換的正確性,必須使用互斥量對(duì)內(nèi)存進(jìn)行加鎖,這種做法勢(shì)必造成性能問(wèn)題。
Go 語(yǔ)言提倡使用通信的方法代替共享內(nèi)存,這里通信的方法就是使用通道(channel)
channel 具有幾個(gè)特性:
1.類似unix中的管道(pipe)
2.先進(jìn)先出
3.線程安全,多個(gè)goroutine同時(shí)訪問(wèn),不需要加鎖
4.channel是有類型的,一個(gè)整數(shù)的channel 只能存放整
2.1使用通道發(fā)送數(shù)據(jù)
通道創(chuàng)建后,就可以使用通道進(jìn)行發(fā)送和接收操作。
1) 通道發(fā)送數(shù)據(jù)的格式
通道的發(fā)送使用特殊的操作符-,將數(shù)據(jù)通過(guò)通道發(fā)送的格式為:
通道變量 - 值
通道變量:通過(guò)make創(chuàng)建好的通道實(shí)例。
值:可以是變量、常量、表達(dá)式或者函數(shù)返回值等。值的類型必須與ch通道的元素類型一致。
2) 通過(guò)通道發(fā)送數(shù)據(jù)的例子
使用 make 創(chuàng)建一個(gè)通道后,就可以使用-向通道發(fā)送數(shù)據(jù),代碼如下:
// 創(chuàng)建一個(gè)空接口通道
ch := make(chan interface{})
// 將0放入通道中
ch - 0
// 將hello字符串放入通道中
ch - "hello"
2.2 使用通道接收數(shù)據(jù)
1)通道接收同樣使用-操作符,通道接收有如下特性:
① 通道的收發(fā)操作在不同的兩個(gè) goroutine 間進(jìn)行。
由于通道的數(shù)據(jù)在沒(méi)有接收方處理時(shí),數(shù)據(jù)發(fā)送方會(huì)持續(xù)阻塞,因此通道的接收必定在另外一個(gè) goroutine 中進(jìn)行。
② 接收將持續(xù)阻塞直到發(fā)送方發(fā)送數(shù)據(jù)。
如果接收方接收時(shí),通道中沒(méi)有發(fā)送方發(fā)送數(shù)據(jù),接收方也會(huì)發(fā)生阻塞,直到發(fā)送方發(fā)送數(shù)據(jù)為止
③ 每次接收一個(gè)元素。
通道一次只能接收一個(gè)數(shù)據(jù)元素。
通道的數(shù)據(jù)接收一共有以下 4 種寫(xiě)法。
2) 阻塞接收數(shù)據(jù)
阻塞模式接收數(shù)據(jù)時(shí),將接收變量作為-操作符的左值,格式如下:
data := -ch
執(zhí)行該語(yǔ)句時(shí)將會(huì)阻塞,直到接收到數(shù)據(jù)并賦值給 data 變量。
3) 非阻塞接收數(shù)據(jù)
使用非阻塞方式從通道接收數(shù)據(jù)時(shí),語(yǔ)句不會(huì)發(fā)生阻塞,格式如下:
data, ok := -ch
data:表示接收到的數(shù)據(jù)。未接收到數(shù)據(jù)時(shí),data 為通道類型的零值。
ok:表示是否接收到數(shù)據(jù)。
非阻塞的通道接收方法可能造成高的 CPU 占用,因此使用非常少。如果需要實(shí)現(xiàn)接收超時(shí)檢測(cè),可以配合 select 和計(jì)時(shí)器 channel 進(jìn)行,可以參見(jiàn)后面的內(nèi)容。
4) 接收任意數(shù)據(jù),忽略接收的數(shù)據(jù)
阻塞接收數(shù)據(jù)后,忽略從通道返回的數(shù)據(jù),格式如下:
-ch
執(zhí)行該語(yǔ)句時(shí)將會(huì)發(fā)生阻塞,直到接收到數(shù)據(jù),但接收到的數(shù)據(jù)會(huì)被忽略。這個(gè)方式實(shí)際上只是通過(guò)通道在 goroutine 間阻塞收發(fā)實(shí)現(xiàn)并發(fā)
2.3 發(fā)生阻塞的2種情況
1)發(fā)送方發(fā)送阻塞:在通道數(shù)據(jù)沒(méi)有接收方處理時(shí),通道的數(shù)據(jù)一開(kāi)始會(huì)存放到固定的數(shù)據(jù)緩沖區(qū)內(nèi),超出緩沖區(qū)的大小將發(fā)生持續(xù)阻塞。
package main
func main() {
var ch chan int
ch = make(chan int, 5) //定義數(shù)據(jù)緩存區(qū)設(shè)置為5個(gè)大小
//將數(shù)據(jù)保存在緩沖區(qū)內(nèi)并不會(huì)發(fā)生當(dāng)前線程阻塞
for i := 0; i 5; i++ {
ch - i
}
//但將第6個(gè)加入通道(超出緩沖區(qū))就會(huì)立即阻塞當(dāng)前的協(xié)程(即main線程) 最后panic
ch - 6
}
這個(gè)程序的執(zhí)行結(jié)果直接painc 因?yàn)樵诠艿兰尤隿h - 6 的時(shí)候因?yàn)榫彺鎱^(qū)沒(méi)有那么大,并且沒(méi)有接收方去消化數(shù)據(jù),故painc。
2) 數(shù)據(jù)接收方發(fā)生阻塞:如果接收方?jīng)]有接收到數(shù)據(jù),接收方等待發(fā)送方發(fā)送數(shù)據(jù),等待的過(guò)程也會(huì)使數(shù)據(jù)接收的協(xié)程發(fā)生阻塞。
package main
import (
"fmt"
"time"
)
func main() {
var ch chan int
ch = make(chan int) //無(wú)定義數(shù)據(jù)緩存區(qū)
go func() {
var a = -ch //執(zhí)行第一次取出
fmt.Println(a)
}()
time.Sleep(time.Second * 4) //主線程等待4才給管道數(shù)據(jù)
ch - 1 //通道里只入一個(gè)數(shù)據(jù)
//接收方協(xié)程是一個(gè)并發(fā)匿名函數(shù)
time.Sleep(time.Second * 5) //主線程等待5秒讓goroute有處理時(shí)間然后結(jié)束
}
這個(gè)程序的執(zhí)行結(jié)果是延時(shí)4秒后控制臺(tái)打印出通道的值1,5秒后主程序結(jié)束。上邊的程序是先讓接收者協(xié)程開(kāi)啟等待接收通道的值,而發(fā)送者是主函數(shù)延遲4秒后才將值放入通道ch,匿名函數(shù)中不得不等待發(fā)送者的值,所以造成了匿名并發(fā)函數(shù)的阻塞。 我們可以思考到,如果去掉4秒等待的時(shí)間, 這個(gè)程序就是使用channel作為協(xié)程之間同步的最簡(jiǎn)單的例子,我們發(fā)現(xiàn)channel同步的特性就是無(wú)數(shù)據(jù)緩存區(qū)。
同樣一個(gè)程序,當(dāng)你把接受者 go func() 程序放到 ch-1 的下邊,就會(huì)painc。 為什么? 以為ch通道并沒(méi)有緩存區(qū),并且接受者還未執(zhí)行。導(dǎo)致painc。
package main
import (
"fmt"
"time"
)
func main() {
var ch chan int
ch = make(chan int) //無(wú)定義數(shù)據(jù)緩存區(qū)
//time.Sleep(time.Second * 4) //主線程等待4才給管道數(shù)據(jù)
ch - 1 //通道里只入一個(gè)數(shù)據(jù)
//接收方協(xié)程是一個(gè)并發(fā)匿名函數(shù)
//一個(gè)并發(fā)執(zhí)行的協(xié)程
go func() {
var a = -ch //執(zhí)行第一次取出
fmt.Println(a)
}()
time.Sleep(time.Second * 5) //主線程等待5秒讓goroute有處理時(shí)間然后結(jié)束
}
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
您可能感興趣的文章:- Go 并發(fā)實(shí)現(xiàn)協(xié)程同步的多種解決方法
- go等待一組協(xié)程結(jié)束的操作方式
- golang協(xié)程池模擬實(shí)現(xiàn)群發(fā)郵件功能
- 解決go在函數(shù)退出后子協(xié)程的退出問(wèn)題
- Go使用協(xié)程交替打印字符
- Golang 之協(xié)程的用法講解
- 淺談golang for 循環(huán)中使用協(xié)程的問(wèn)題
- Go并發(fā):使用sync.WaitGroup實(shí)現(xiàn)協(xié)程同步方式