在Go中,我们可以使用原子函数和互斥锁来保证对共享资源和安全访问以及消除竞争状态,还可以利用通道,通过发送和接收共享的资源,在goroutine进行同步

当一个资源需要在goroutine之间进行共享的时候,通道在goroutine进行架起了一个管道,通道在goroutine之间架起了管道,确保了交换数据的机制,声明通道的时候,我们需要在声明的使用指定共享的数据类型,通过通道共享内置类型,命令类型,结构类型,引用类型的值或者类型

创建通道的方式如下

//无缓冲

unbuffered := make(chan int)

//有缓冲

buffered := make(chan string, 10)

我们创建了两个内置函数,一个无缓冲的通道,一个有缓冲的通道

我们创建一个有缓冲的通道,第二个参数指定这个指定通道的缓冲区的大小

然后往通道如松值的代码如下,需要用到 <- 操作符

buffered := make(chan string,10)

//发送一个数据

buffered <- “Gopher”

我们创建了一个10个值的缓冲区,之后我们通过通道发送字符串 “Gopher”

为了另一个goroutine之后能够接受到这个字符串,我们仍然需要使用 <- 操作符

接收一个值,我们可以如下的字符串

value := <- buffered

为了接收一个值, <- 运算符要在操作的通道变量的左侧

通道中,有缓冲的和无缓冲的,是具有不同的行为的

对于无缓冲的通.在接收前没有能力保存任何值的通道

要求的是发送的goroutine接收的goroutne同时准备好,也就是队列中0存储的那一类型

我们看一下goroutine如何通过无缓冲的通道共享的一个值,如果没有人交换,那么会一直锁住

图片

我们看一串代码

package main

import (

“fmt”

“math/rand”

“sync”

“time”

)

// wg is used to wait for the program to finish.

var wg sync.WaitGroup

func init() {

rand.Seed(time.Now().UnixNano())

}

// main is the entry point for all Go programs.

func main() {

// Create an unbuffered channel.

court := make(chan int)

// Add a count of two, one for each goroutine.

wg.Add(2)

// Launch two players.

go player(“Nadal”, court)

go player(“Djokovic”, court)

// Start the set.

court <- 1

// Wait for the game to finish.

wg.Wait()

}

// player simulates a person playing the game of tennis.

func player(name string, court chan int) {

// Schedule the call to Done to tell main we are done.

defer wg.Done()

for {

// Wait for the ball to be hit back to us.

ball, ok := <-court

if !ok {

// If the channel was closed we won.

fmt.Printf(“Player %s Won\n”, name)

return

}

// Pick a random number and see if we miss the ball.

n := rand.Intn(100)

if n%13 == 0 {

fmt.Printf(“Player %s Missed\n”, name)

// Close the channel to signal we lost.

close(court)

return

}

// Display and then increment the hit count by one.

fmt.Printf(“Player %s Hit %d\n”, name, ball)

ball++

// Hit the ball back to the opposing player.

court <- ball

}

}

这端代码中,我们创建了一个int类型的无缓冲的通道,让两个goroutine在击球的时候互相同步,然后在28和29行,创建了两个goroutine

然后分别的传递数值给这两个goroutine

然后用不同的模型,使用无缓冲的通道,在goroutine之间同步数据,在main函数的17行,创建了无缓冲的通道,用来来回的传递,我们设置了WaitGroup为1,然后我们不断的传递,直到函数返回之前,阻塞这个WaitGroup的,等待一位跑步者完成比赛

在Runner goroutine里面,可以看到接力棒如何在跑步者中传递,一旦接力棒传了,就会创建一个新跑步者,然后接力下一棒,直到四个跑者都完成了,才会锁住goroutine,等待交接完成

然后是有缓冲的通道,在消费之前能够存储一个或者多个值的通道,这种类型的通道并不会强制要求goroutine之间同时发送和接收,整体的接收流程如下

图片

接下来是一个使用缓冲的通道的例子

package main

import (

“fmt”

“math/rand”

“sync”

“time”

)

const (

numberGoroutines = 4 // Number of goroutines to use.

taskLoad = 10 // Amount of work to process.

)

// wg is used to wait for the program to finish.

var wg sync.WaitGroup

// init is called to initialize the package by the

// Go runtime prior to any other code being executed.

func init() {

// Seed the random number generator.

rand.Seed(time.Now().Unix())

}

// main is the entry point for all Go programs.

func main() {

// Create a buffered channel to manage the task load.

tasks := make(chan string, taskLoad)

// Launch goroutines to handle the work.

wg.Add(numberGoroutines)

for gr := 1; gr <= numberGoroutines; gr++ {

go worker(tasks, gr)

}

// Add a bunch of work to get done.

for post := 1; post <= taskLoad; post++ {

tasks <- fmt.Sprintf(“Task : %d”, post)

}

// Close the channel so the goroutines will quit

// when all the work is done.

close(tasks)

// Wait for all the work to get done.

wg.Wait()

}

// worker is launched as a goroutine to process work from

// the buffered channel.

func worker(tasks chan string, worker int) {

// Report that we just returned.

defer wg.Done()

for {

// Wait for work to be assigned.

task, ok := <-tasks

if !ok {

// This means the channel is empty and closed.

fmt.Printf(“Worker: %d : Shutting Down\n”, worker)

return

}

// Display we are starting the work.

fmt.Printf(“Worker: %d : Started %s\n”, worker, task)

// Randomly wait to simulate work time.

sleep := rand.Int63n(100)

time.Sleep(time.Duration(sleep) * time.Millisecond)

// Display we finished the work.

fmt.Printf(“Worker: %d : Completed %s\n”, worker, task)

}

}

创建了一个缓冲容量为10 的string类型的通道,然后设置了一个WaitGroup为4

然后在代码中,我们关闭了通道,但是没有结束代码这是因为go语言中,通道关闭了,只是不能存入数据,但是可以从中取出缓冲的全部值,这是因为通道中保存了所有的数据

在worker函数中,我们利用了一个无限的for循环,处理所有接收到的工作,直到利用ok标志,来检查通道是否清空而且已经关闭,直到ok标志位false,说明接收的值是无效的

发表评论

邮箱地址不会被公开。 必填项已用*标注