3

【基础】Golang并发编程

 2 years ago
source link: https://wintrysec.github.io/2021/09/26/%E3%80%905%E3%80%91%E5%AE%89%E5%85%A8%E5%BC%80%E5%8F%91/Golang/%E3%80%90%E5%9F%BA%E7%A1%80%E3%80%91Golang%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Go并发编程

Golang 通过信道来共享变量的值,避免了直接在不同的线程间共享变量;

在任何时间点只有一个协程有权访问变量的值;在设计上就杜绝了数据竞争的情况。

记住:不要通过共享内存来通信,而应该通过通信来共享内存

Go 程(goroutine)

Go 程(goroutine)是由 Go 运行时管理的轻量级线程。

go f(x, y, z)
//启动一个新的 Go 程并执行

f, x, yz 的求值发生在当前的 Go 程中,而 f 的执行发生在新的 Go 程中。

Go 程在相同的地址空间中运行,因此在访问共享的内存时必须进行同步。

sync 包提供了这种能力,不过在 Go 中并不经常用到,因为还有其它的办法。

作用:可能临时需要用一次,创建一个无名的函数,然后立即调用它(临时工)

一个简单的匿名函数如下:

func main()  {
go count(5,"\033[32;1m 牛")
func(){
fmt.Println("这是个匿名函数")
}()
}

协程同步(sync)

golang中当main函数运行结束后,所有go协程(goroutine)都会被终止。

所以需要进行协程同步,等所有协程结束后再终止程序。

package main

import (
"fmt"
"sync"
"time"
)

func main() {
var wg sync.WaitGroup
wg.Add(1) //计数器加一
go func(){
count(5,"🐂")
wg.Done() //计数器减一
}()

go func(){
count(5,"🐏")
wg.Done() //计数器减一
}()

wg.Wait() //等待操作结束,为0时立即返回
}

func count(n int,animal string) {
for i :=0;i<n;i++{
fmt.Println(i+1, animal)
time.Sleep(time.Millisecond * 500)
}

}

但是两个Goroutine完全可能会在同一时刻被执行,共同操作一块内存就会死锁;

Go中用信道来进行协程间通信。

信道(Channels)

信道是带有类型的管道,你可以通过它用信道操作符 <- 来发送或者接收值。

“箭头”就是数据流的方向

ch <- v    // 将 v 发送至信道 ch。
v := <- ch // 从 ch 接收值并赋予 v。

和映射与切片一样,信道在使用前必须创建:

ch := make(chan int)

默认情况下,发送和接收操作在另一端准备好之前都会阻塞。

这使得 Go 程可以在没有显式的锁或竞态变量的情况下进行同步。


以下示例对切片中的数进行求和,将任务分配给两个 Go 程。

一旦两个 Go 程完成了它们的计算,它就能算出最终的结果。

package main

import "fmt"

func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 将和送入 c
}

func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)

go sum(s[:len(s)/2], c) //17
go sum(s[len(s)/2:], c) //-5
x, y := <-c, <-c // 从 c 中接收

fmt.Println(x, y, x+y) //结果是堆栈形式,先进后出(猜测)
}

带缓冲的信道

信道可以是 带缓冲的

将缓冲长度作为第二个参数提供给 make 来初始化一个带缓冲的信道:

ch := make(chan int, 100)

仅当信道的缓冲区填满后,向其发送数据时才会阻塞。当缓冲区为空时,接受方会阻塞。

修改示例填满缓冲区,然后看看会发生什么。

ppackage main

import (
"fmt"
)

func write(ch chan int) {
for i := 0; i < 100; i++ {
ch <- i
fmt.Println("successfully wrote", i, "to ch")
}
close(ch)
}
func main() {
ch := make(chan int, 10) //修改这里的值能看到明显的阻塞效果
go write(ch)
//time.Sleep(2 * time.Second)
for v := range ch {
fmt.Println("read value", v,"from ch")
//time.Sleep(2 * time.Second)
}
}

range 和 close

发送者可通过 close 关闭一个信道来表示没有需要发送的值了。

接收者可以通过为接收表达式分配第二个参数来测试信道是否被关闭:若

没有值可以接收且信道已被关闭,那么在执行完

c, ok := <-ch

此时 ok 会被设置为 false

循环 for i := range c 会不断从信道接收值,直到它被关闭。

注意:

只有发送者才能关闭信道,而接收者不能。

向一个已经关闭的信道发送数据会引发程序恐慌(panic)。

还要注意:

信道与文件不同,通常情况下无需关闭它们。

只有在必须告诉接收者不再有需要发送的值时才有必要关闭,例如终止一个 range 循环。

package main

import (
"fmt"
)

func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}

func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}

select 语句

select 语句使一个 Go 程可以等待多个通信操作。

select 会阻塞到某个分支可以继续执行为止,这时就会执行该分支。

当多个分支都准备好时会随机选择一个执行。

package main

import "fmt"

func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}

func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}

select 中的其它分支都没有准备好时,default 分支就会执行。

为了在尝试发送或者接收时不发生阻塞,可使用 default 分支:

select {
case i := <-c:
// 使用 i
default:
// 从 c 中接收会阻塞时执行
}
package main

import (
"fmt"
"time"
)

func main() {
tick := time.Tick(100 * time.Millisecond)
boom := time.After(500 * time.Millisecond)
for {
select {
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("BOOM!")
return
default:
fmt.Println(" .")
time.Sleep(50 * time.Millisecond)
}
}
}

sync.Mutex

我们已经看到信道非常适合在各个 Go 程间进行通信。

但是如果我们并不需要通信呢?比如说,若我们只是想保证每次只有一个 Go 程能够访问一个共享的变量,从而避免冲突?

这里涉及的概念叫做 互斥(mutual exclusion) ,我们通常使用 互斥锁(Mutex) 这一数据结构来提供这种机制。

Go 标准库中提供了 sync.Mutex 互斥锁类型及其两个方法:

  • Lock
  • Unlock

我们可以通过在代码前调用 Lock 方法,在代码后调用 Unlock 方法来保证一段代码的互斥执行。参见 Inc 方法。

我们也可以用 defer 语句来保证互斥锁一定会被解锁。参见 Value 方法。

package main

import (
"fmt"
"sync"
"time"
)

// SafeCounter 的并发使用是安全的。
type SafeCounter struct {
v map[string]int
mux sync.Mutex
}

// Inc 增加给定 key 的计数器的值。
func (c *SafeCounter) Inc(key string) {
c.mux.Lock()
// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
c.v[key]++
c.mux.Unlock()
}

// Value 返回给定 key 的计数器的当前值。
func (c *SafeCounter) Value(key string) int {
c.mux.Lock()
// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
defer c.mux.Unlock()
return c.v[key]
}

func main() {
c := SafeCounter{v: make(map[string]int)}
for i := 0; i < 1000; i++ {
go c.Inc("somekey")
}

time.Sleep(time.Second)
fmt.Println(c.Value("somekey"))
}

Recommend

  • 61
    • blog.lichfaker.com 6 years ago
    • Cache

    Golang并发编程 | LichFaker的博客

    Goroutine在Go语言中,语言本身就已经实现和支持了并发, 我们只需要通过go关键字来开启goroutine即可。 gouroutine其实就是一种协程,类似其他语言中的coroutine, 是在编译器或虚拟机层面上的多任务。它可以运行在一个或多个线程上,但不同于线程,它是非抢占式...

  • 47
    • www.tuicool.com 5 years ago
    • Cache

    Golang 并发编程与 Context

    Context 是 Golang 中非常有趣的设计,它与 Go 语言中的并发编程有着比较密切的关系,在其他语言中我们很难见到类似 Context 的东西,它不仅能够用来设置截止日期、同步『信号』还能用来传递请求相关的值。

  • 41
    • ricstudio.top 4 years ago
    • Cache

    Java 并发编程基础 ① - 线程

    一、什么是线程 进程是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,线程则是进程的一个执行路径,一个进程中至少有一个线程,进程中的多个线程 共享进程的资源 。 操作...

  • 31
    • segmentfault.com 3 years ago
    • Cache

    Java并发编程-线程基础

    1. 线程的创建 首先我们来复习我们学习 java 时接触的线程创建,这也是面试的时候喜欢问的,有...

  • 5

    synchronized同步关键字简介 synchronized是属于JVM层面的一个关键字,底层是通过一个monitor对象(管程对象)来完成,由于wait()/notify()等方法也依赖于monitor对象,所以只有在同步的块或者方法中才能调用w...

  • 3

    并发编程基础底层原理学习(二) 进程就是应用...

  • 7

    并发编程基础底层原理学习(三)

  • 4

    并发编程基础底层原理学习(四) 在程序执行时,为了提...

  • 4

    相信大家以前在做阅读理解的时候,一定有从老师那里学一个技巧或者从参考答案看个:结合上下文。根据上下文我们能够找到有助于解题的相关信息,也能更加了解段落的思想。在开发过程中,也有这个上下文(Context)的概念,而且上下文也必不可...

  • 7

    并发编程防御装-锁(基础版) 大家好,我是小高先生。在Java并发编程的世界中,锁的地位至关重要。它就像是一道坚固的防线,确保了并发编程运行结果的正确性。你可以不准备攻击装备,但是锁这个防御装备是必不可少的。相信大...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK