GO并发详解
Posted 就想吃饱饭
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GO并发详解相关的知识,希望对你有一定的参考价值。
并发和并行
并发:逻辑上具有处理多个任务的能力。一般并发的数量要小于CPU的数量,这些并发的任务通过间隔执行的方式来执行,这里一般是在单核CPU上面。
并行:物理上具备处理多个任务的能力。物理CPU的核数和并行的任务数相同,是并发的理想目标,理论上同一时刻内一个CPU执行一个任务。
计算机是如何实现并发的?
计算机的分时调用是并发的根本,CPU通过快速的切换作业来执行不同的作业,基本的调度单位在执行的时候可以被阻塞掉,此时就会将CPU资源让出来,等到该调度单位再次被唤醒的时候,又可以使用CPU资源,而操作系统保证了整个的调度过程。
线程,进程和协程
进程:是系统资源分配的最小单位,系统是由一个个进程组成的,包括文本区,数据区和堆栈区。进程的创建和销毁都比较消耗资源和时间。进程是抢占式的争夺CPU的资源,单核CPU在同一时刻只能有一个进程在执行。
线程:是CPU调度的最小单位,线程属于进程,它共享进程的整个内存空间。多线程是不安全的,进程内的一个线程崩溃会导致整个进程崩溃。
协程:协程是属于线程的,协程的程序是在线程里面跑的。协程没有线程的上下文切换,协程的切换是程序员自己控制的。协程是原子操作的,不存在代码执行一半,被终止的情况。
进程切换分两步
1.切换页目录以使用新的地址空间
2.切换内核栈和硬件上下文。
对于linux来说,线程和进程的最大区别就在于地址空间。
对于线程切换,第1步是不需要做的,第2是进程和线程切换都要做的。
Goroutine
Go语言里的一种轻量级线程—协程。
- 相对线程,协程的优势就在于它非常轻量级,进行上下文切换的代价非常的小。
- 对于一个goroutine ,每个结构体G中有一个sched的属性就是用来保存它上下文的。这样,goroutine 就可以很轻易的来回切换。
- 由于其上下文切换在用户态下发生,根本不必进入内核态,所以速度很快。而且只有当前goroutine 的 PC, SP等少量信息需要保存。
- 在Go语言中,每一个并发的执行单元为一个goroutine。
Go 语言之goroutine的调度原理
golang 并发 && chan
channels 是 goroutines之间通信的工具, 可以理解为管道, 虽然go也提供共享变量的方式, 但是更加推荐使用channel
channel是有类型的,并且还有方向,可以是单向的,也可以是双向的,类似于unix中的pipe(管道)。
channel是通过<-和->来完成读写操作的
channel<-value(表示往channel写数据
<-channel表示从channel读数据
package main
import (
"fmt"
"runtime"
)
func GO(c chan bool, i int) {
a := 1
for i := 0; i < 100000; i++{
a += i
}
fmt.Println(i, a)
c <- true //向chan发送
}
func mian() {
// 调整并发的运行性能 最大效率地利用CPU
runtime.GOMAXPROCS(runtime.NumCPU())
c := make(chan bool, 10)
for i := 0; i<10; i++{
go GO(c, i)
}
for i := 0; i<10; i++{
<-c // 取数据
}
}
golang 并发 && Sync
Sync包同步提供基本的同步原语,如互斥锁。 除了Once和WaitGroup类型之外,大多数类型都是供低级库例程使用的。 通过Channel和沟通可以更好地完成更高级别的同步。并且此包中的值在使用过后不要拷贝。
waitgroup 用来等待一组goroutines的结束,在主Goroutine里声明,并且设置要等待的goroutine的个数,每个goroutine执行完成之后调用 Done,最后在主Goroutines 里Wait即可。
sync.WaitGroup提供了三个方法:
Add:添加或减少goroutine的数量。
Done:相当于Add(-1)。
Wait:阻塞住等待WaitGroup数量变成0.
package mian
import (
"fmt"
"runtime"
"sync"
)
/*
sync.WaitGroup提供了三个方法:
Add:添加或减少goroutine的数量。
Done:相当于Add(-1)。
Wait:阻塞住等待WaitGroup数量变成0.
*/
func GO(wg *sync.WaitGroup, i int) {
a := 1
for i := 0; i < 100000; i++{
a += i
}
fmt.Println(i, a)
wg.Done()
}
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
wg := sync.WaitGroup{}
wg.Add(10)
for i := 0; i<10; i++{
go GO1(&wg, i)
}
wg.Wait()
}
golang 并发 && Select
select是Go语言中的一个控制语句,它有select,case, default共同构成
select 使用场景
协程之间的通讯采用的是channel。channel不管是有缓存的,还是无缓存的都会有阻塞的情况出现,只不过无缓存的阻塞会更加频繁。而协程长时间阻塞了之后,Go语言本身又没有提供这种超时的解决机制,所以开发者需要自己考虑实现这种超时机制。这种超时机制在Go语言中则是使用select来解决的。
使用select 向chan发送
package mian
import (
"fmt"
)
// 使用select 向chan发送
func mian() {
c := make(chan int)
go func() {
for v := range c{
fmt.Println(v)
}
}()
for {
select {
case c<-0: //随机发送
case c<-1:
}
}
}
使用select 取数 设置超时 或者 default
package mian
import (
"fmt"
"time"
)
// 使用select 取出
func mian() {
c, c1, c2 := make(chan bool, 2), make(chan int), make(chan string)
go func() {
for {
fmt.Println("FOR -------------------------------------")
select {
case v, ok := <-c1:
if !ok{
fmt.Println("c1 != ok")
c <- true
break
}
fmt.Println("c1",v)
case v, ok := <-c2:
if !ok{
fmt.Println("c2 != ok")
c <- true
break
}
fmt.Println("c2",v)
case <-time.After(3 * time.Second): //设置超时
c <- true
fmt.Println("超时")
//default: // 注释掉 有def时 chan没有数据 就一直 default, so就不走超时case
// fmt.Println("NO case ok")
// time.Sleep(time.Second * 3)
}
}
}()
c1 <- 1
c2 <- "hello"
c1 <- 2
c2 <- "word"
// 注释下面关闭chan操作时,会走超时的case
//close(c1)
//close(c2)
for i:[Go] 通过 17 个简短代码片段,切底弄懂 channel 基础