并发
在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,才继续往下运行

                
            
        
浙公网安备 33010602011771号