go語言channel,理解Go的Goroutine和channel

 2023-11-30 阅读 28 评论 0

摘要:原址 ? 進程,線程的概念在操作系統的書上已經有詳細的介紹。進程是內存資源管理和cpu調度的執行單元。為了有效利用多核處理器的優勢,將進程進一步細分,允許一個進程里存在多個線程,這多個線程還是共享同一片內存空間,但cpu調度的最小單元

原址

? 進程,線程的概念在操作系統的書上已經有詳細的介紹。進程是內存資源管理和cpu調度的執行單元。為了有效利用多核處理器的優勢,將進程進一步細分,允許一個進程里存在多個線程,這多個線程還是共享同一片內存空間,但cpu調度的最小單元變成了線程。

那協程又是什么東西,以及與線程的差異性??

協程,可以看作是輕量級的線程。但與線程不同的是,線程的切換是由操作系統控制的,而協程的切換則是由用戶控制的。go語言channel?

最早支持協程的程序語言應該是lisp方言scheme里的continuation(續延),續延允許scheme保存任意函數調用的現場,保存起來并重新執行。Lua,C#,python等語言也有自己的協程實現。

go的goroutinue

今天要講的goroutinue,本質上就是協程。但有兩點不同:
1. goroutinue可以實現并行,也就是說,多個協程可以在多個處理器同時跑。而協程同一時刻只能在一個處理器上跑(把宿主語言想象成單線程的就好了)。go channel原理?

2. goroutine之間的通信是通過channel,而協程的通信是通過yield和resume()操作。


在Go里實現goroutine非常簡單,只需要在函數的調用前面加關鍵字go即可,

go doSth()

下面的例子演示,啟動10個goroutines分別打印索引。

package main
import (
"fmt"
"time"	
)func main() {for i:=1;i<10;i++ {go func(i int) {fmt.Println(i)}(i)}//暫停一會,保證打印全部結束time.Sleep(1e9)
}

在分析goroutine執行的隨機性和并發性,把goroutine看作是java的守護線程是完全可以的。上面的例子中,啟動了10個goroutine,再加上main函數的主goroutine,總共有11個goroutines。由于goroutine類似于”守護線程“,如果主goroutine不等待片刻,可能程序就沒有輸出打印了。go channel實現原理?上面的例子輸出如下:(輸出的索引是完全隨機的)



go的channel

在java的世界里,并發主要是靠鎖住臨界資源(共享內存)來保證同步的。而channel則是goroutinues之間進行通信的利器。

channel可以形象比喻為工廠里的傳送帶,一頭的生產者goroutine往傳輸帶放東西,另一頭的消費者goroutinue則從輸送帶取東西。channel實際上是一個有類型的消息隊列,遵循先進先出的特點。

1. channel的操作符號

ch <- ele 表示ele被發送給channel ch;

ele2 <- ch 表示從channel ch取一個值,然后賦給ele2

2. 阻塞式channel

channel默認是沒有緩沖區的,也就是說,通信是阻塞的。golang中的channel面試編程題,send操作必須等到有消費者accept才算完成。

舉個栗子

package main
import "fmt"func main() {ch1 := make(chan int)go pump(ch1) // pump hangsfmt.Println(<-ch1) // prints only 0
}func pump(ch chan int) {for i:= 0; ; i++ {ch <- i}
}

上面代碼pump()里的channel在接受到第一個元素后就被阻塞了,直到主goroutinue拿走了數據。最終channel阻塞在接受第二個元素,程序只打印 0

3 帶有buff的channel

沒有buff的channel只能容納一個元素,而帶有buff的channel則可以非阻塞容納N個元素。發送數據到buffed channel不會被阻塞,除非channel已滿;同樣的,從buffed channel取數據也不會被阻塞,除非channel空了。這有點像java的ConcurrentLinkedQueue。


goroutine和channel的應用

結合goroutine和channel,可以模擬出java處理并發情況的若干情景

1. 實現future

package main
import "fmt"
import "time"func main() {future := heavyCalculation()fmt.Println(<-future)
}func heavyCalculation() (chan int) {future := make(chan int)go func() {//模擬耗時計算time.Sleep(1e9)future <- 666}()return future
}

2. 實現CountDownLatch

package main
import "fmt"func main() {nTask := 5ch := make(chan int)for i:=1;i<=nTask;i++ {go doTask(ch)}for i:=1;i<=nTask;i++ {<-ch}fmt.Println("finished all tasks")
}func doTask(ch chan<- int) {//doSth...ch<- 0
}


3. 并發訪問對象

Hashtable是線程安全的,意味著多條線程同時操作hashtable對象是不會引起狀態不一致的。查看Hashtable源代碼可知,其幾乎全部方法都添加synchronized關鍵字,例如put(),remove()操作。在go里,我們可以在對象內部保存一個函數類型的channel,涉及對象狀態的操作都放入channel里,對象初始化的時候開啟一條goroutinue,不停地執行匿名函數。

package main
import (
"fmt"
"strconv"
"time"	
)type Person struct {Name stringsalary float64chF chan func()
}
func NewPerson(name string, salary float64) *Person {p := &Person{name, salary, make(chan func())}go p.backend()return p
}
func (p *Person) backend() {for f := range p.chF {f()}
}func (p *Person) AddSalary(sal float64) {p.chF <- func() { p.salary += sal }  // (ThreadSafe)// p.salary += sal (NotThreadSafe)
}func (p *Person) ReduceSalary(sal float64) {p.chF <- func() { p.salary -= sal }  // (ThreadSafe)// p.salary -= sal (NotThreadSafe)
}func (p *Person) Salary() float64 {fChan := make(chan float64)p.chF <- func() { fChan <- p.salary }return <-fChan
}
func (p *Person) String() string {return p.Name + " - salary is: " + strconv.FormatFloat(p.Salary(), 'f', 2, 64)
}func main() {p := NewPerson("Kingston", 8888.8)fmt.Println(p)for i:=1;i<=500;i++ {go func() {p.AddSalary(1);}()}for i:=1;i<=500;i++ {go func() {p.ReduceSalary(1);}()}time.Sleep(3e9)fmt.Println("After changed:")fmt.Println(p)
}


4. 生產者消費者模式

每次涉及到并發情景,都喜歡用生產者消費者模式,因為它太經典啦

2個面包師同時生產面包,5個顧客同時取面包(盡管以下例子的打印不能說明并發的真實情況,因為channel的操作和打印的組合不是原子操作,但不影響程序的邏輯)

package main
import (
"fmt"
"time"
)func main() {bread := make(chan int,3)for i:=1;i<=2;i++ {go produce(bread)}for i:=1;i<=5;i++ {go consume(bread)}time.Sleep(1e9)
}func produce(ch chan<- int) {for {ch <- 1fmt.Println("produce bread")time.Sleep(100 * time.Millisecond)}
}func consume(ch <-chan int) {for {<-chfmt.Println("take bread")time.Sleep(200 * time.Millisecond)}
}

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/4/185614.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息