首頁 > 軟體

Golang協程常見面試題小結

2023-02-28 18:01:39

交替列印奇數和偶數

下面讓我們一起來看看golang當中常見的演演算法面試題
使用兩個goroutine交替列印1-100之間的奇數和偶數, 輸出時按照從小到大輸出.

方法一:使用無緩衝的channel進行協程間通訊

package main

import (
    "fmt"
    "sync"
)

// PrintOddAndEven1 /*
func PrintOddAndEven1() {
    //方法一,使用無緩衝的channel進行通訊
    var wg = new(sync.WaitGroup) //注意這裡需要是指標go語言當中都是值傳遞
    wg.Add(2)
    ch := make(chan struct{}) //無緩衝channel
    defer close(ch)
    maxVal := 100
    go func() {
        defer wg.Done()
        for i := 1; i <= maxVal; i++ {
            ch <- struct{}{}
            if i%2 == 1 { //奇數
                fmt.Printf("the odd is %dn", i)

            }
        }
    }()

    go func() {
        defer wg.Done()
        for i := 1; i <= maxVal; i++ {
            <-ch          //從管道當中讀取一個資料
            if i%2 == 0 { //偶數
                fmt.Printf("the even is %dn", i)

            }
        }
    }()
    wg.Wait()

}
func main() {
    PrintOddAndEven1()
    fmt.Println("over")
}

下面博主來解釋一下這個的原理 首先因為變數ch是一個無緩衝的channel, 所以只有讀寫同時就緒時才不會阻塞。所以兩個goroutine會同時進入各自的 if 語句(此時 i 是相同的),但是此時只能有一個 if 是成立的,不管goroutine快,都會由於讀channel或寫channel導致阻塞,因此程式會交替列印1-100且有順序。

方法二:使用有緩衝的channel

func PrintOddAndEven2() {
    var wg = new(sync.WaitGroup) //注意這裡需要是指標go語言當中都是值傳遞
    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() { //奇數協程
        defer wg.Done()
        for i := 1; i <= maxVal; i += 2 {
            <-oddChan
            fmt.Printf("the odd print %dn", i)
            eveChan <- struct{}{} //通知偶數協程
        }
    }()

    go func() {
        //偶數協程
        defer wg.Done()
        for i := 2; i <= maxVal; i += 2 {
            <-eveChan
            fmt.Printf("the even print %dn", i)
            oddChan <- struct{}{} //通知奇數協程可以列印了
        }
    }()
    wg.Wait()

}

func main() {
    PrintOddAndEven2()
    fmt.Println("over")
}

第二個方法使用這個有緩衝的channel。有緩衝的channel當容量沒有達到上限時寫入不會阻塞在這裡奇數協程的channel容量為1我們提前給他寫入了一個資料因此當偶數和奇數協程都開始讀取資料時,首先讀取到資料的是奇數協程,奇數協程列印完之後在通知偶數協程列印,偶數協程列印完成之後在通知奇數協程重複下去就實現了交替列印的效果。

N個協程列印1到maxVal

題目描述非常的簡單就是N個協程交替列印1到maxVal。比如N=3,maxVal是這個100效果應該是第一個協程列印1,第二個協程列印2,第三個協程列印3,第一個協程列印4這樣的效果。
這道題看起來非常的複雜,博主第一次看到這個題的時候也感覺很複雜但是仔細想一下其實並沒有那麼複雜和上面兩題的解題思路是一樣的。下面我們看看這個程式碼如何實現

package main

import (
    "fmt"
    "sync"
)

func main() {
    maxVal := 10
    res := 0                        //用於列印數位
    N := 3                          //協程的數量
    exitChan := make(chan struct{}) //用於退出
    chanArr := make([]chan struct{}, N)
    for i := 0; i < N; i++ {
        //使用無緩衝的channel
        chanArr[i] = make(chan struct{}, 1)
    }
    num := 0 //記錄輪到那個協程開始列印了
    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個協程列印%dn", i, res)
                if num == N-1 {//已經迴圈一輪了輪到第0個協程列印資料了
                    num = 0
                } else {
                    num++
                }
                res++
                chanArr[num] <- struct{}{} //第num個協程可以列印資料了

            }

        }(i)
    }
    <-exitChan
    for i := 0; i < N; i++ {
        close(chanArr[i]) //將管道全部關閉否則會有協程洩漏
    }

}

其實也非常的簡單也是利用channel來進行這個協程之間的通訊,由於是N個協程之間進行通訊所以了我們定義一個channel的切片首先往第一個channel當中寫入一個資料其他管道沒有寫入資料那麼最先列印的一定是這個第一個協程然後我們在利用一個計數器通知其他協程列印。最後需要注意的是主協程退出時需要將管道全部關閉否則其他協程一致阻塞在那裡就會引起協程洩漏,就只能等到gc的時候才能回收。

交替列印字元和數位

問題描述: 使用兩個 goroutine 交替列印序列,一個 goroutinue 列印數位, 另外一個goroutine列印字母, 最終效果如下 12AB34CD56EF78GH910IJ 。

如果鐵子們上面兩題會了那麼這道題就是有手就行的那種和第一道題沒有啥區別

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()

}

同樣的也是利用這個channe進行通訊,利用有緩衝的channel進行通訊。當然也能使用這個無緩衝的channel進行通訊

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()

交替列印字串

題目描述,給定一個字串使用兩個協程交替列印它。
如果老鐵們上面的拿到題都會了這道題不就是和第一道題是這個一模一樣的嗎?廢話不多說直接上程式碼

方法一使用無緩衝的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()

}

當然也可以使用有緩衝的channel在這裡鐵子們可以自行編寫上面寫的太多了。

三個協程列印ABC

題目描述使用三個協程分別列印A,B,C列印這個100次。
本題的難度和上面那幾個題完全是一個貨色,我們可以使用三個有緩衝的channel就可以達到目的了。具體細節請看程式碼

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
}

在這裡需要注意的點是我們需要close掉這個管道當達到臨界值時,主協程退出但是defer方法會執行這個時候管道一關閉所有協程都會收到退出訊號,另外兩個阻塞在那裡的協程就會退出這樣就沒有這個協程洩漏了。

並行將多個檔案合併到一個檔案當中

MergeFile 把多個檔案合成一個檔案,並行實現子協程優雅退出。採用並行的方式
本題的解題思路同樣的也非常的簡單我們可以定義一個管道並行的讀取檔案寫入到管道當中然後再並行的寫入到檔案當中。非常的簡單

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
    "strconv"
    "sync"
)

// MergeFile 把多個檔案合成一個檔案,並行實現子協程優雅退出

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') //注意已經包含換行符了
        if err != nil {
            if err == io.EOF {
                if len(line) > 0 {
                    line += "n"
                    fileChan <- line
                }
                break
            } else {
                fmt.Println(err)
                break
            }
        } else if line == "rn" {
            fmt.Println("進來")
            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)//注意需要關閉因為已經沒有人往裡面寫了
    //            for line:=range fileChan{
    //                writer.WriteString(line) //讀取時候已經包含換行符了
    //            }
    //            break LOOP
    //        case line := <-fileChan:
    //            writer.WriteString(line) //讀取時候已經包含換行符了
    //        }
    //
    //    }
    for {
        if line, ok := <-fileChan; ok {
            if line != "rn" {
                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練習

啟動一個協程生成100個數傳送到ch1管道當中,再啟動一個協程從ch1當中取值然後計算平方將其放入ch2管道當中主協程列印

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)
    }

}

到此這篇關於Golang協程常見面試題小結的文章就介紹到這了,更多相關Golang協程面試題內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com