并发

在go中实现并发使用的是goroutine,类似于线程,但是goroutine是go的运行时runtime调度和管理的,不需要自己去写线程、进程、协程这些玩意。

01 基本使用

  • 1 函数

语法

go 函数()

注意:

​ 一个goroutine必定对应一个函数,可以创建多个goroutine去执行相同的函数。

package main

import (
	"fmt"
	"time"
)

func main() {
	go HelloWorld()
	go HelloWorld()
	fmt.Println("main") // 结果只打印了main
}

func HelloWorld() {
	fmt.Println("hello world")

}

注意:

​ 当main()函数运行完毕后,会把该mian()函数中启动的goroutine全部停止

02 GOMAXPROCS

指定程序并发时占用的CPU核数,也就是使用多少线程来同时运行go的代码。

go1.5之前默认是单核执行,之后默认的是机器上的CPU核数。

  • 单核时,一个运行完之后,另一个才开始运行
package main

import (
	"fmt"
	"runtime"
	"time"
)

func main() {
	runtime.GOMAXPROCS(1)
	go HelloWorld()
	go a()
	fmt.Println("main")
	time.Sleep(time.Second)
}

func HelloWorld() {
	for i := 1; i < 10; i++ {
		fmt.Println(i, "hello world")
	}
}

func a() {
	for i := 1; i < 10; i++ {
		fmt.Println(i, "a")
	}
}

注意:

​ 先运行a函数,运行完在运行HelloWorld函数

  • 多核时,同时运行
package main

import (
	"fmt"
	"runtime"
	"time"
)

func main() {
	runtime.GOMAXPROCS(2)
	go HelloWorld()
	go a()
	fmt.Println("main")
	time.Sleep(time.Second)
}

func HelloWorld() {
	for i := 1; i < 10; i++ {
		fmt.Println(i, "hello world")
	}
}

func a() {
	for i := 1; i < 10; i++ {
		fmt.Println(i, "a")
	}
}

03 channal

channal是一种类型,是引用类型

channal实现goroutine之间的通信

go是通过通讯共享内存

1 定义channel类型

语法

var 变量名 chan channel中的数据类型
package main

import "fmt"

func main()  {
	var c chan NT
	fmt.Println(c)
}

type NT interface {
}

2 channel初始化

必须初始化才能使用,用make函数初始化

package main

import "fmt"

func main() {
	fmt.Println("ok")
	ChannelFunc()
}

func ChannelFunc() {
	var a chan int // 生命channel变量
  a = make(chan int, 8) // 初始化channel变量,相当于a := make(chan int, 8)
	fmt.Println(a)
}

3 channel操作

是管道,先进先出

共三种操作:

​ 1 加值

​ 2 取值

​ 3 关闭管道

注意:

​ 加值和取值都用<-表示

语法

# 加值
"""
a := make(chan int, 8) // 8是通道的容量,也就是缓冲,当缓冲的余值为0时,再往通道里加数据会报错
a <- 10  
"""

# 取值
"""
b := <- a
"""

# 关闭
"""
close(a)
"""
package main

import "fmt"

func main() {
	ChannelFunc()
}

func ChannelFunc() {
	a := make(chan int, 8)
	a <- 10
	a <- 18
	b := <-a
	close(a)
	fmt.Println(b)
}

注意:

​ 1 通道是会被垃圾回收机制回收的,所以手动关闭通道不是必须的

​ 2 关闭通道后,继续向关闭的通道加数据,会报错

​ 3 关闭通道后,可以向关闭的通道取数据,数据取完会取指定类型的零值

​ 4 关闭的通道再关闭操作,会报错

例子:

package main

import "fmt"

func main() {
	ChannelFunc()
}

func ChannelFunc() {
	a := make(chan int, 8)
	a <- 10
	a <- 18
	close(a)
	b := <- a // 10
	c := <- a // 18
	d := <- a // 0
	f := <- a // 0
	fmt.Println(b, c, d, f)
}

4 通道通信

  • 没缓冲的通道

不能直接向通道里加数据,需要用一个goroutine去接收

package main

import (
	"fmt"
)

func main() {
	a := make(chan int)
	go Recv(a) // 必须要在加入值之前开启一个goroutine去取数据
	a <- 10
	fmt.Println("main")
}

func Recv(a chan int)  {
	c := <- a
	fmt.Println(c)
}
  • 有缓冲的通道

可以直接向管道里加数据

package main

import (
	"fmt"
)

func main() {
	a := make(chan int, 10)
	a <- 10
	fmt.Println("main")
}
  • for range 从通道中取值
package main

import "fmt"

func main() {
	c := make(chan int)
	go Send(c)
	for v := range c {
		fmt.Println(v)
	}
}

func Send(c chan int) {
	for i := 0; i < 1000; i++ {
		c <- i
	}
	close(c)
}

注意:

​ 开启接收数据的goroutine一般放在向通道内加数据的前面

  • 单向通道

定义通道时,单向通道可以分为

只 写:chan <- 类型

只读:<- chan 类型

package main

import "fmt"

func main() {
	c := make(chan int)
	go Send(c)
	Recv(c)
	fmt.Println("main")
}

func Send(c chan<- int) {
	for i := 0; i < 1000; i++ {
		c <- i
	}
	close(c)
}

func Recv(c <-chan int) {
	for v := range c {
		fmt.Println(v)
	}
}

04 select的多路复用

同时从多个通道内取值

package main

import (
	"fmt"
)

func main() {
	c := make(chan int, 100)
	for i := 0; i < 100; i++ {
		select {
		case a := <-c:
			fmt.Println("a: ", a)
		case c <- i:
			fmt.Println("i: ", i)
		default:
			fmt.Println("fail")
		}
	}
}

注意:

​ 1 处理多个通道的:取值或者加值的操作

​ 2 同时满足多个case时,随意选择一个

05 锁

多个goroutine同时操作同一个数据时,会造成脏数据,数据竞态问题

互斥锁

06 并发任务同步

等待所有的goroutine的运行完毕,再继续往下运行(再结束):sync.WaitGroup

循环调用goroutine,且用管道接收返回结果,拿到结果后才继续往下执行

package main

import (
	"fmt"
	"sync"
)

func main() {
	var ws sync.WaitGroup
  // 用有缓存的管道,不发生阻塞
	numChan := make(chan int, 10)
	for i := 1; i < 20; i++ {
		ws.Add(1) //引用计数加一
		go goTest(i, 10, numChan, &ws) // ws对象必须是指针
	}
	go func() { // 这里必须是一个新的goroutine,异步处理关闭管道的操作。确保关闭管道之前,所有的数据加入完毕。
		ws.Wait() // 会在这里发生阻塞,引用计数为0时才会继续往下执行
		close(numChan) // 关闭管道。保证关闭后不会再有新的数据添加
	}()
	for v := range numChan { // 获取管道中的数据
		fmt.Println(v)
	}
}

func goTest(num1, num2 int, numChan chan int, ws *sync.WaitGroup) {
	defer ws.Done() // 引用计数减1
	numChan <- num1 + num2 // 加入管道
	return
}

注意:

Add(数字):计数器加数字,一般情况下是1

Done():计数器减一

Wait():当计数器为0,才继续往下运行

posted @ 2021-03-09 23:31  tianzhh_lynn  阅读(16)  评论(0编辑  收藏  举报