Go 并发编程 Goroutines,Channels,sync 包

Goroutines

在 Go 语言中,每一个并发的执行单元叫作一个 goroutine。使用 go 关键字即可以创建一个 goroutine,使得我们能够并发执行一些任务:

go func() () {

// ...

}()

goroutine是并发执行的,不会阻塞下面的操作。但如果我们使用了多个goroutine,并且想要等待这些goroutine全都运行完毕再执行下一步的操作,这时可以使用sync.WaitGroup

wg := sync.WaitGroup{}

wg.Add(1)

go func() () {

defer wg.Done()

// ...

}()

wg.Add(1)

go func() () {

defer wg.Done()

// ...

}()

...

wg.Wait() // 等待上面的goroutine都执行完毕

Channels

一个 channel 是一个通信机制,它可以让一个goroutine通过它给另一个goroutine发送值信息。每个channel都有一个特殊的类型,也就是channels可发送数据的类型。一个可以发送int类型数据的channel一般写为chan int。一个channel有发送和接受两个主要操作

ch := make(chan int) // ch has type 'chan int',是一个不带缓存的channel

ch <- x // a send statement

x = <-ch // a receive expression in an assignment statement

<-ch // a receive statement; result is discarded

for x := range ch {

fmt.Println(x) // channel支持range操作,当channel关闭且没有值可接收时for循环退出

}

close(ch) // 关闭操作

x, ok := <-ch // 对于已经关闭的channel,ok为false

Channel还支持close操作,用于关闭channel,随后对基于该channel的任何发送操作都将导致panic异常。对一个已经被close过的channel进行接收操作依然可以接受到之前已经成功发送的数据;如果channel中已经没有数据的话将产生一个零值的数据。试图重复关闭一个channel将导致panic异常,试图关闭一个nil值的channel也将导致panic异常。

channel分为带缓存的和不带缓存的两种。一个基于无缓存Channels的发送操作将导致发送者goroutine阻塞,直到另一个goroutine在相同的Channels上执行接收操作;反之,如果接收操作先发生,那么接收者goroutine也将阻塞,直到有另一个goroutine在相同的Channels上执行发送操作。

通过ch := make(chan string, 3)的方式可以创建一个带缓存的channel,如果内部缓存队列是满的,那么发送操作将阻塞直到因另一个goroutine执行接收操作而释放了新的队列空间。相反,如果channel是空的,接收操作将阻塞直到有另一个goroutine执行发送操作而向队列插入元素。内置的cap()可以获取channel的容量,而len()可以获取channel中有效元素的个数。

可以通过带缓存的管道来实现最大并发数控制:

var limit = make(chan int, 3)

func main() {

for _, w := range work {

go func() {

limit <- 1

w()

<-limit

}()

}

select{}

}

select语句会选择case中能够执行的语句去执行,有点类似switch,如果多个case同时就绪时,select会随机地选择一个执行

for {

select {

case <-ch1:

// ...

case x := <-ch2:

// ...use x...

case ch3 <- y:

// ...

case <-time.After(time.Second):

// ... timeout

default:

// ...

return

}

}

需要注意的是,select中如果用了break,跳出的只是select,而不是外面的for循环

sync.Mutex

var mu sync.Mutex

func DoSomething() {

mu.Lock() // 如果已经上锁,则这一步会阻塞直到锁被释放

defer mu.Unlock()

// ...

}

除此之外,还有sync.RWMutexsync.Once也提供了很不错的特性。

sync.RWMutex提供了读写锁,一般有以下几种情况:

  • 在没有写锁的情况下,读锁是无阻塞的
  • 写锁之间互斥,存在写锁,则其他写锁阻塞
  • 写锁和读锁之间互斥,存在写锁则读锁阻塞,存在读锁则写锁阻塞

使用sync.RWMutex加读写锁的方法:

  • Lock 加写锁;Unlock 释放写锁
  • RLock 加读锁;RUnlock 释放读锁

sync.Once一般用于初始化变量,好处是可以在代码任意位置用到的时候再初始化,而不一定要放在init中,并且并发场景下是线程安全的,基于sync.Once重新实现单例模式:

var (

instance *singleton

once sync.Once

)

func Instance() *singleton {

once.Do(func() {

instance = &singleton{}

})

return instance

}

以上是 Go 并发编程 Goroutines,Channels,sync 包 的全部内容, 来源链接: utcz.com/z/264412.html

回到顶部