如果我们给每一个方法都执行协程,那么我们需要在主函数处多等等添加个睡眠
为什么需要在后面添加一个休眠?
因为主函数结束,那么所有的线程都最将会被销毁,我们添加休眠的目的就是给线程续命。
package main import ( "fmt" "time" ) func showMsg(msg string) { /*** 发现打印机的方式- 输入一个停顿一会会 */ for i := 0; i < len(msg); i++ { fmt.Printf("%c", msg[i]) time.Sleep(time.Millisecond * 500) //睡眠 100毫秒 } } func main() { go showMsg("java") go showMsg("golang") time.Sleep(time.Second * 5) //我们需要在这里添加一个睡眠 }
(3).协程原理探究
Go文件的协程实质上就是: 开辟一个新的线程。
主函数退出之后,那么线程也就会自动关闭
2.并发编程之通道 channel
Go 提供了一种称为通道的机制,用于在coroutinue(协程)之间共享数据。当您为coroutinue执行并发活动时,需要受coroutinue之间共享资源或数据,通道充当coroutinue之间的管道(通道)并提供一种机制来保护同步交互
。
需要在声明通道时指定数据类型。我们可以共享内置、命名、结构和引用类型的值和指针。数据在通道上传递: 在任何给定时间只有一个coroutinue可以访问数据项: 因此按照设计不会发生数据竞争。
根据数据交互的行为,有两种类型的通道: 无缓冲通道和缓冲通道。无缓冲通道用于执行coroutinue之间的同步通信,而缓冲通道用于执行异步通信。无缓冲通道保证在发送和接受发生的瞬间执行两个coroutinue之间的交换。缓冲通道没有这样的保证。
通道由make函数创建,该函数指定 chan 关键字和通道的元素类型。
(1).创建无缓存通道和缓存通道
1. 无缓存 ->同步 Unbuffed:=make(chan int) //整形无缓存通道 2. 有缓存 ->异步 buffed:=make(chan int,10) //整形有缓冲通道
(2).通道的发送和接受
- 通道的发送
通道变量名<-xxxx
- 通道的接受
xxx:=<-通道变量名
(3).通道的发送和接受特性
- 对于同一个通道,发送操作之间是互斥的,接受操作之间也是互斥的
- 发送操作和接受操作中对元素值的处理是不可分割的
- 发送操作在完全完成之前会被阻塞,接受操作也是如此
如果不添加go 进行协程实现那么会出现: 死锁错误
package main import ( "fmt" "math/rand" "time" ) var values = make(chan int) //创建一个int类型的无缓存区的通道 func send() { rand.Seed(time.Now().UnixNano()) //获取随机数,不加随机种子,每次遍历获取都是重复的一些随机数据 value := rand.Intn(10) // 随机取一个10范围之内的数 fmt.Printf("send:%v\n", value) time.Sleep(time.Second * 5) //阻塞五秒 values <- value //把值放到通道中 } func main() { defer close(values) //关闭通道 go send() //协程执行这个方法 **** 如果不协程执行会报错 println("wait...") value := <-values //取值 fmt.Printf("receive:%v\n", value) println("end...") }
3.并发编程之WaitGroup实现同步
waitGroup: 就是指所有的线程实现完毕之后,再继续执行。
(1).等待组的步骤
1. 我们需要引入包 import "sync" 2. 定义等待组的全局变量 var wp sync.WaitGroup //定义等待组 3. 在开启协助的下面我们需要向等待组中+1 wp.Add(1) //每次启动一个线程我们就向等待组中添加1 4. 在线程执行的函数最后向等待组-1 defer wp.Done() //每一个线程完成之后我们就在等待组中减少1 5. 在主线程最后进行等待 wp.Wait() //在这里实时等待,直到等待组中的值为-1,终止执行。 比睡眠更加高效
(2).等待组的实列
package main import ( "fmt" "sync" ) var wp sync.WaitGroup //定义等待组 func showMsg(i int) { defer wp.Done() //每一个线程完成之后我们就在等待组中减少1 fmt.Printf("%v\n", i) } func main() { // 开启十个协程 for i := 0; i < 10; i++ { go showMsg(i) wp.Add(1) //每次启动一个线程我们就向等待组中添加1 } // 主协程: 假如我们不在主线程这里设置阻塞的话,那么他就会直接关闭。不会等待其他线程执行完毕 wp.Wait() //在这里实时等待,直到等待组中的值为-1,终止执行。 比睡眠更加高效。 }
输出结果: 直到所有的等待组全部完成即可
(3).等待组的原理
假如我们开启一个线程,就向等待组中元素+1.如果一个等待组中的一个线程完成了,那么就申请向等待组元素-1.
4.并发编程之runtime包
(1).runtime.Gosched()
让出CPU时间片,重新等待安排任务。
只是给一个机会,但是结果不一定。
package main import ( "fmt" "runtime" ) func show(msg string) { for i := 0; i < 2; i++ { fmt.Printf("%v\n", msg) } } func main() { // 子线程 go show("小弟") for i := 0; i < 2; i++ { runtime.Gosched() // 我是老大,给你个机会能够超越我 -》只是机会 println("老大") } println("end....") }
结果: 谁先输出不一定看运气
(2).runtime.Goexit()
退出当前协程
package main import ( "fmt" "runtime" "time" ) func show(msg string) { for i := 0; i < 10; i++ { if i >= 5 { runtime.Goexit() //直接退出这个协程 } fmt.Printf("%v\n", msg) } } func main() { // 子线程 go show("小弟") time.Sleep(time.Second * 1) //阻塞一秒 println("end....") }
结果: 执行到第五个,线程直接终止
(3).runtime.GOMAXPROCS(n)
设定执行程序的最大CPU数是多少?
package main import ( "fmt" "runtime" "time" ) func a() { for i := 0; i < 10; i++ { fmt.Printf("a:%v\t", i) } } func b() { for i := 0; i < 10; i++ { fmt.Printf("b:%v\t", i) } } func main() { fmt.Printf("runtime.numcpu:%v\n", runtime.NumCPU()) //默认是最大CPU进行执行操作 runtime.GOMAXPROCS(2) //可以指定CPU的数量 go a() go b() time.Sleep(time.Second) }
5.并发编程之Mutex互斥锁实现同步
除了使用channel实现同步之外,还可以使用Mutex互斥的方式实现同步
(1).Mutex使用步骤
1. 导入包 import "sync" 2. 定义变量 var lock sync.Mutex //定义锁 3. 上锁 lock.Lock() //上锁 4. 解锁 lock.Unlock() //解锁
(2).Mutex的实列
package main import ( "fmt" "sync" "time" ) var i int = 100 var wt sync.WaitGroup //设置等待组 var lock sync.Mutex //定义锁 func add() { defer wt.Done() //放在这个子线程的最后执行 time.Sleep(time.Millisecond * 10) lock.Lock() //上锁 i += 1 fmt.Printf("i++ ->%v\n", i) lock.Unlock() //解锁 } func sub() { defer wt.Done() //放在这个子线程的最后执行 lock.Lock() //上锁 i -= 1 time.Sleep(time.Millisecond * 10) //先等会-因为可能还没有解锁 fmt.Printf("i-- ->%v\n", i) lock.Unlock() //解锁 } func main() { for i := 0; i < 100; i++ { wt.Add(1) //+1 go add() wt.Add(1) //+1 go sub() } wt.Wait() fmt.Printf("end ->%v", i) }
输出结果: 要么i++先输出完毕,要么i–先输出完毕