Golang協(xié)程常見面試題小結(jié)
交替打印奇數(shù)和偶數(shù)
下面讓我們一起來(lái)看看golang當(dāng)中常見的算法面試題
使用兩個(gè)goroutine交替打印1-100之間的奇數(shù)和偶數(shù), 輸出時(shí)按照從小到大輸出.
方法一:使用無(wú)緩沖的channel進(jìn)行協(xié)程間通信
package main import ( ?? ?"fmt" ?? ?"sync" ) // PrintOddAndEven1 /* func PrintOddAndEven1() { ?? ?//方法一,使用無(wú)緩沖的channel進(jìn)行通信 ?? ?var wg = new(sync.WaitGroup) //注意這里需要是指針go語(yǔ)言當(dāng)中都是值傳遞 ?? ?wg.Add(2) ?? ?ch := make(chan struct{}) //無(wú)緩沖channel ?? ?defer close(ch) ?? ?maxVal := 100 ?? ?go func() { ?? ??? ?defer wg.Done() ?? ??? ?for i := 1; i <= maxVal; i++ { ?? ??? ??? ?ch <- struct{}{} ?? ??? ??? ?if i%2 == 1 { //奇數(shù) ?? ??? ??? ??? ?fmt.Printf("the odd is %d\n", i) ?? ??? ??? ?} ?? ??? ?} ?? ?}() ?? ?go func() { ?? ??? ?defer wg.Done() ?? ??? ?for i := 1; i <= maxVal; i++ { ?? ??? ??? ?<-ch ? ? ? ? ?//從管道當(dāng)中讀取一個(gè)數(shù)據(jù) ?? ??? ??? ?if i%2 == 0 { //偶數(shù) ?? ??? ??? ??? ?fmt.Printf("the even is %d\n", i) ?? ??? ??? ?} ?? ??? ?} ?? ?}() ?? ?wg.Wait() } func main() { ?? ?PrintOddAndEven1() ?? ?fmt.Println("over") }
下面博主來(lái)解釋一下這個(gè)的原理 首先因?yàn)樽兞縞h是一個(gè)無(wú)緩沖的channel, 所以只有讀寫同時(shí)就緒時(shí)才不會(huì)阻塞。所以兩個(gè)goroutine會(huì)同時(shí)進(jìn)入各自的 if 語(yǔ)句(此時(shí) i 是相同的),但是此時(shí)只能有一個(gè) if 是成立的,不管goroutine快,都會(huì)由于讀channel或?qū)慶hannel導(dǎo)致阻塞,因此程序會(huì)交替打印1-100且有順序。
方法二:使用有緩沖的channel
func PrintOddAndEven2() { ?? ?var wg = new(sync.WaitGroup) //注意這里需要是指針go語(yǔ)言當(dāng)中都是值傳遞 ?? ?wg.Add(2) ?? ?oddChan := make(chan struct{}, 1) ?? ?eveChan := make(chan struct{}, 1) ?? ?defer close(oddChan) ?? ?defer close(eveChan) ?? ?oddChan <- struct{}{} ?? ?maxVal := 20 ?? ?go func() { //奇數(shù)協(xié)程 ?? ??? ?defer wg.Done() ?? ??? ?for i := 1; i <= maxVal; i += 2 { ?? ??? ??? ?<-oddChan ?? ??? ??? ?fmt.Printf("the odd print %d\n", i) ?? ??? ??? ?eveChan <- struct{}{} //通知偶數(shù)協(xié)程 ?? ??? ?} ?? ?}() ?? ?go func() { ?? ??? ?//偶數(shù)協(xié)程 ?? ??? ?defer wg.Done() ?? ??? ?for i := 2; i <= maxVal; i += 2 { ?? ??? ??? ?<-eveChan ?? ??? ??? ?fmt.Printf("the even print %d\n", i) ?? ??? ??? ?oddChan <- struct{}{} //通知奇數(shù)協(xié)程可以打印了 ?? ??? ?} ?? ?}() ?? ?wg.Wait() } func main() { ?? ?PrintOddAndEven2() ?? ?fmt.Println("over") }
第二個(gè)方法使用這個(gè)有緩沖的channel。有緩沖的channel當(dāng)容量沒有達(dá)到上限時(shí)寫入不會(huì)阻塞在這里奇數(shù)協(xié)程的channel容量為1我們提前給他寫入了一個(gè)數(shù)據(jù)因此當(dāng)偶數(shù)和奇數(shù)協(xié)程都開始讀取數(shù)據(jù)時(shí),首先讀取到數(shù)據(jù)的是奇數(shù)協(xié)程,奇數(shù)協(xié)程打印完之后在通知偶數(shù)協(xié)程打印,偶數(shù)協(xié)程打印完成之后在通知奇數(shù)協(xié)程重復(fù)下去就實(shí)現(xiàn)了交替打印的效果。
N個(gè)協(xié)程打印1到maxVal
題目描述非常的簡(jiǎn)單就是N個(gè)協(xié)程交替打印1到maxVal。比如N=3,maxVal是這個(gè)100效果應(yīng)該是第一個(gè)協(xié)程打印1,第二個(gè)協(xié)程打印2,第三個(gè)協(xié)程打印3,第一個(gè)協(xié)程打印4這樣的效果。
這道題看起來(lái)非常的復(fù)雜,博主第一次看到這個(gè)題的時(shí)候也感覺很復(fù)雜但是仔細(xì)想一下其實(shí)并沒有那么復(fù)雜和上面兩題的解題思路是一樣的。下面我們看看這個(gè)代碼如何實(shí)現(xiàn)
package main import ( ?? ?"fmt" ?? ?"sync" ) func main() { ?? ?maxVal := 10 ?? ?res := 0 ? ? ? ? ? ? ? ? ? ? ? ?//用于打印數(shù)字 ?? ?N := 3 ? ? ? ? ? ? ? ? ? ? ? ? ?//協(xié)程的數(shù)量 ?? ?exitChan := make(chan struct{}) //用于退出 ?? ?chanArr := make([]chan struct{}, N) ?? ?for i := 0; i < N; i++ { ?? ??? ?//使用無(wú)緩沖的channel ?? ??? ?chanArr[i] = make(chan struct{}, 1) ?? ?} ?? ?num := 0 //記錄輪到那個(gè)協(xié)程開始打印了 ?? ?chanArr[0] <- struct{}{} ?? ?for i := 0; i < N; i++ { ?? ??? ?go func(i int) { ?? ??? ??? ?for { ?? ??? ??? ??? ?<-chanArr[i] ?? ??? ??? ??? ?if res >= maxVal { ?? ??? ??? ??? ??? ?exitChan <- struct{}{} ?? ??? ??? ??? ??? ?break ?? ??? ??? ??? ?} ?? ??? ??? ??? ?fmt.Printf("第%d個(gè)協(xié)程打印%d\n", i, res) ?? ??? ??? ??? ?if num == N-1 {//已經(jīng)循環(huán)一輪了輪到第0個(gè)協(xié)程打印數(shù)據(jù)了 ?? ??? ??? ??? ??? ?num = 0 ?? ??? ??? ??? ?} else { ?? ??? ??? ??? ??? ?num++ ?? ??? ??? ??? ?} ?? ??? ??? ??? ?res++ ?? ??? ??? ??? ?chanArr[num] <- struct{}{} //第num個(gè)協(xié)程可以打印數(shù)據(jù)了 ?? ??? ??? ?} ?? ??? ?}(i) ?? ?} ?? ?<-exitChan ?? ?for i := 0; i < N; i++ { ?? ??? ?close(chanArr[i]) //將管道全部關(guān)閉否則會(huì)有協(xié)程泄漏 ?? ?} }
其實(shí)也非常的簡(jiǎn)單也是利用channel來(lái)進(jìn)行這個(gè)協(xié)程之間的通信,由于是N個(gè)協(xié)程之間進(jìn)行通信所以了我們定義一個(gè)channel的切片首先往第一個(gè)channel當(dāng)中寫入一個(gè)數(shù)據(jù)其他管道沒有寫入數(shù)據(jù)那么最先打印的一定是這個(gè)第一個(gè)協(xié)程然后我們?cè)诶靡粋€(gè)計(jì)數(shù)器通知其他協(xié)程打印。最后需要注意的是主協(xié)程退出時(shí)需要將管道全部關(guān)閉否則其他協(xié)程一致阻塞在那里就會(huì)引起協(xié)程泄漏,就只能等到gc的時(shí)候才能回收。
交替打印字符和數(shù)字
問(wèn)題描述: 使用兩個(gè) goroutine 交替打印序列,一個(gè) goroutinue 打印數(shù)字, 另外一個(gè)goroutine打印字母, 最終效果如下 12AB34CD56EF78GH910IJ 。
如果鐵子們上面兩題會(huì)了那么這道題就是有手就行的那種和第一道題沒有啥區(qū)別
func main() { ?? ?numChan := make(chan struct{}, 1) ?? ?chChan := make(chan struct{}, 1) ?? ?defer close(numChan) ?? ?defer close(chChan) ?? ?var wg sync.WaitGroup ?? ?wg.Add(2) ?? ?numChan <- struct{}{} ?? ?go func() { ?? ??? ?defer wg.Done() ?? ??? ?for num := 1; num <= 26; num++ { ?? ??? ??? ?<-numChan ?? ??? ??? ?fmt.Printf("%d", num) ?? ??? ??? ?chChan <- struct{}{} ?? ??? ?} ?? ?}() ?? ?go func() { ?? ??? ?defer wg.Done() ?? ??? ?for ch := 'A'; ch <= 'Z'; ch++ { ?? ??? ??? ?<-chChan ?? ??? ??? ?fmt.Printf("%s", string(ch)) ?? ??? ??? ?numChan <- struct{}{} ?? ??? ?} ?? ?}() ?? ?wg.Wait() }
同樣的也是利用這個(gè)channe進(jìn)行通信,利用有緩沖的channel進(jìn)行通信。當(dāng)然也能使用這個(gè)無(wú)緩沖的channel進(jìn)行通信
func main() { ?? ?numChan := make(chan struct{}) ?? ?defer close(numChan) ?? ?var wg sync.WaitGroup ?? ?wg.Add(2) ?? ?go func() { ?? ??? ?defer wg.Done() ?? ??? ?for num := 1; num <= 26; num++ { ?? ??? ??? ?numChan <- struct{}{} ?? ??? ??? ?fmt.Printf("%d", num) ?? ??? ?} ?? ?}() ?? ?go func() { ?? ??? ?defer wg.Done() ?? ??? ?for ch := 'A'; ch <= 'Z'; ch++ { ?? ??? ??? ?<-numChan ?? ??? ??? ?fmt.Printf("%s", string(ch)) ?? ??? ?} ?? ?}() ?? ?wg.Wait()
交替打印字符串
題目描述,給定一個(gè)字符串使用兩個(gè)協(xié)程交替打印它。
如果老鐵們上面的拿到題都會(huì)了這道題不就是和第一道題是這個(gè)一模一樣的嗎?廢話不多說(shuō)直接上代碼
方法一使用無(wú)緩沖的channel
func main() { ?? ?chChan := make(chan struct{}) ?? ?defer close(chChan) ?? ?var wg = new(sync.WaitGroup) ?? ?wg.Add(2) ?? ?str := "hello world" ?? ?N := len(str) ?? ?go func() { ?? ??? ?defer wg.Done() ?? ??? ?for i := 0; i < N; i++ { ?? ??? ??? ?chChan <- struct{}{} ?? ??? ??? ?if i%2 == 0 { ?? ??? ??? ??? ?fmt.Println(string(str[i])) ?? ??? ??? ?} ?? ??? ?} ?? ?}() ?? ?go func() { ?? ??? ?defer wg.Done() ?? ??? ?for i := 0; i < N; i++ { ?? ??? ??? ?<-chChan ?? ??? ??? ?if i%2 == 1 { ?? ??? ??? ??? ?fmt.Println(string(str[i])) ?? ??? ??? ?} ?? ??? ?} ?? ?}() ?? ?wg.Wait() }
當(dāng)然也可以使用有緩沖的channel在這里鐵子們可以自行編寫上面寫的太多了。
三個(gè)協(xié)程打印ABC
題目描述使用三個(gè)協(xié)程分別打印A,B,C打印這個(gè)100次。
本題的難度和上面那幾個(gè)題完全是一個(gè)貨色,我們可以使用三個(gè)有緩沖的channel就可以達(dá)到目的了。具體細(xì)節(jié)請(qǐng)看代碼
package main import ( ?? ?"fmt" ?? ?"sync" ) func main() { ?? ?Achan := make(chan struct{}, 1) ?? ?Bchan := make(chan struct{}, 1) ?? ?Cchan := make(chan struct{}, 1) ?? ?defer close(Achan) ?? ?defer close(Bchan) ?? ?defer close(Cchan) ?? ?Achan <- struct{}{} ?? ?counter := 0 ?? ?maxVal := 10 ?? ?exitChan := make(chan struct{}) //用于退出 ?? ?go func() { ?? ??? ?for { ?? ??? ??? ?<-Achan ?? ??? ??? ?if counter >= maxVal { ?? ??? ??? ??? ?exitChan <- struct{}{} ?? ??? ??? ??? ?break ?? ??? ??? ?} ?? ??? ??? ?fmt.Printf("%s ", "A") ?? ??? ??? ?counter++ ?? ??? ??? ?Bchan <- struct{}{} ?? ??? ?} ?? ?}() ?? ?go func() { ?? ??? ?for { ?? ??? ??? ?<-Bchan ?? ??? ??? ?if counter >= maxVal { ?? ??? ??? ??? ?exitChan <- struct{}{} ?? ??? ??? ??? ?break ?? ??? ??? ?} ?? ??? ??? ?fmt.Printf("%s ", "B") ?? ??? ??? ?counter++ ?? ??? ??? ?Cchan <- struct{}{} ?? ??? ?} ?? ?}() ?? ?go func() { ?? ??? ?for { ?? ??? ??? ?<-Cchan ?? ??? ??? ?if counter >= maxVal { ?? ??? ??? ??? ?exitChan <- struct{}{} ?? ??? ??? ??? ?break ?? ??? ??? ?} ?? ??? ??? ?fmt.Printf("%s ", "C") ?? ??? ??? ?counter++ ?? ??? ??? ?Achan <- struct{}{} ?? ??? ?} ?? ?}() ?? ?<-exitChan }
在這里需要注意的點(diǎn)是我們需要close掉這個(gè)管道當(dāng)達(dá)到臨界值時(shí),主協(xié)程退出但是defer方法會(huì)執(zhí)行這個(gè)時(shí)候管道一關(guān)閉所有協(xié)程都會(huì)收到退出信號(hào),另外兩個(gè)阻塞在那里的協(xié)程就會(huì)退出這樣就沒有這個(gè)協(xié)程泄漏了。
并發(fā)將多個(gè)文件合并到一個(gè)文件當(dāng)中
MergeFile 把多個(gè)文件合成一個(gè)文件,并發(fā)實(shí)現(xiàn)子協(xié)程優(yōu)雅退出。采用并發(fā)的方式
本題的解題思路同樣的也非常的簡(jiǎn)單我們可以定義一個(gè)管道并發(fā)的讀取文件寫入到管道當(dāng)中然后再并發(fā)的寫入到文件當(dāng)中。非常的簡(jiǎn)單
package main import ( ?? ?"bufio" ?? ?"fmt" ?? ?"io" ?? ?"os" ?? ?"strconv" ?? ?"sync" ) // MergeFile 把多個(gè)文件合成一個(gè)文件,并發(fā)實(shí)現(xiàn)子協(xié)程優(yōu)雅退出 var fileChan = make(chan string, 10000) var writeFish = make(chan struct{}) var wg sync.WaitGroup func readFile(fileName string) { ?? ?fin, err := os.Open(fileName) ?? ?if err != nil { ?? ??? ?fmt.Println(err.Error()) ?? ??? ?return ?? ?} ?? ?defer fin.Close() ?? ?defer wg.Done() ?? ?reader := bufio.NewReader(fin) ?? ?for { ?? ??? ?line, err := reader.ReadString('\n') //注意已經(jīng)包含換行符了 ?? ??? ?if err != nil { ?? ??? ??? ?if err == io.EOF { ?? ??? ??? ??? ?if len(line) > 0 { ?? ??? ??? ??? ??? ?line += "\n" ?? ??? ??? ??? ??? ?fileChan <- line ?? ??? ??? ??? ?} ?? ??? ??? ??? ?break ?? ??? ??? ?} else { ?? ??? ??? ??? ?fmt.Println(err) ?? ??? ??? ??? ?break ?? ??? ??? ?} ?? ??? ?} else if line == "\r\n" { ?? ??? ??? ?fmt.Println("進(jìn)來(lái)") ?? ??? ??? ?continue ?? ??? ?} else { ?? ??? ??? ?fileChan <- line ?? ??? ?} ?? ?} } func writeFile(fileName string) { ?? ?fout, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) ?? ?if err != nil { ?? ??? ?fmt.Println(err) ?? ??? ?return ?? ?} ?? ?defer fout.Close() ?? ?defer func() { ?? ??? ?close(writeFish) ?? ?}() ?? ?writer := bufio.NewWriter(fout) ?? ?//LOOP: ?? ?//?? ?for { ?? ?//?? ??? ?select { ?? ?//?? ??? ?case <-readFish: ?? ?//?? ??? ??? ?close(fileChan)//注意需要關(guān)閉因?yàn)橐呀?jīng)沒有人往里面寫了 ?? ?//?? ??? ??? ?for line:=range fileChan{ ?? ?//?? ??? ??? ??? ?writer.WriteString(line) //讀取時(shí)候已經(jīng)包含換行符了 ?? ?//?? ??? ??? ?} ?? ?//?? ??? ??? ?break LOOP ?? ?//?? ??? ?case line := <-fileChan: ?? ?//?? ??? ??? ?writer.WriteString(line) //讀取時(shí)候已經(jīng)包含換行符了 ?? ?//?? ??? ?} ?? ?// ?? ?//?? ?} ?? ?for { ?? ??? ?if line, ok := <-fileChan; ok { ?? ??? ??? ?if line != "\r\n" { ?? ??? ??? ??? ?writer.WriteString(line) ?? ??? ??? ?} ?? ??? ?} else { ?? ??? ??? ?break ?? ??? ?} ?? ?} ?? ?writer.Flush() //刷新 } func main() { ?? ?wg.Add(3) ?? ?for i := 1; i <= 3; i++ { ?? ??? ?fileName := "Dir/" + strconv.Itoa(i) ?? ??? ?go readFile(fileName) ?? ?} ?? ?go writeFile("Dir/merge") ?? ?wg.Wait() ?? ?close(fileChan) ?? ?<-writeFish }
Channel練習(xí)
啟動(dòng)一個(gè)協(xié)程生成100個(gè)數(shù)發(fā)送到ch1管道當(dāng)中,再啟動(dòng)一個(gè)協(xié)程從ch1當(dāng)中取值然后計(jì)算平方將其放入ch2管道當(dāng)中主協(xié)程打印
package main import ( ?? ?"fmt" ?? ?"sync" ) var wg sync.WaitGroup func f1(ch1 chan int) { ?? ?defer wg.Done() ?? ?for i := 0; i < 50; i++ { ?? ??? ?ch1 <- i ?? ?} ?? ?close(ch1) } func f2(ch2 chan int, ch1 chan int) { ?? ?defer wg.Done() ?? ?defer close(ch2) ?? ?for x := range ch1 { ?? ??? ?ch2 <- x * x ?? ?} } func main() { ?? ?wg.Add(2) ?? ?a := make(chan int, 50) ?? ?b := make(chan int, 50) ?? ?go f1(a) ?? ?go f2(b, a) ?? ?wg.Wait() ?? ?for x := range b { ?? ??? ?fmt.Println(x) ?? ?} }
到此這篇關(guān)于Golang協(xié)程常見面試題小結(jié)的文章就介紹到這了,更多相關(guān)Golang協(xié)程面試題內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語(yǔ)言k8s?kubernetes使用leader?election實(shí)現(xiàn)選舉
這篇文章主要為大家介紹了Go語(yǔ)言?k8s?kubernetes?使用leader?election選舉,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10Go語(yǔ)言中的基礎(chǔ)數(shù)據(jù)類型使用實(shí)例
這篇文章主要為大家介紹了Go中的基礎(chǔ)數(shù)據(jù)類型使用示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04Hugo 游樂(lè)場(chǎng)內(nèi)容初始化示例詳解
這篇文章主要為大家介紹了Hugo 游樂(lè)場(chǎng)內(nèi)容初始化示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02go-zero源碼閱讀之布隆過(guò)濾器實(shí)現(xiàn)代碼
布隆過(guò)濾器可以用于檢索一個(gè)元素是否在一個(gè)集合中。它的優(yōu)點(diǎn)是空間效率和查詢時(shí)間都比一般的算法要好的多,缺點(diǎn)是有一定的誤識(shí)別率和刪除困難,這篇文章主要介紹了go-zero源碼閱讀-布隆過(guò)濾器,需要的朋友可以參考下2023-02-02Go語(yǔ)言之重要數(shù)組類型切片(slice)make,append函數(shù)解讀
這篇文章主要介紹了Go語(yǔ)言之重要數(shù)組類型切片(slice)make,append函數(shù)用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09Go語(yǔ)言標(biāo)準(zhǔn)錯(cuò)誤error全面解析
Go語(yǔ)言中的錯(cuò)誤處理是通過(guò)內(nèi)置的error接口來(lái)實(shí)現(xiàn)的,其中errorString和wrapError是兩種常見的錯(cuò)誤類型實(shí)現(xiàn)方式,errorString通過(guò)errors.New()方法實(shí)現(xiàn),而wrapError則通過(guò)fmt.Errorf()方法實(shí)現(xiàn),支持錯(cuò)誤的嵌套和解析2024-10-10