GO语言并发编程 并行和并发 并行:指在同一时刻,有多条指令在多个处理器上同时执行
并发:指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,宏观同时执行,微观快速交替执行
GO语言并发的优势 从语言层面就支持了并发。go提供了自动垃圾回收机制,不用关心内存管理问题
goroutine goroutine是什么 它是GO并发设计的核心,是协程,但是比线程更小,可以同时运行成千上万个并发任务
协程和线程的区别 线程:cpu切换多个进程的时候,会花费不少的时间,因为切换进程需要切换到内核态,而每次调度需要内核态都需要读取用户态的数据,进程一旦多起来,cpu调度会消耗一大堆资源,因此引入了线程的概念,线程本身几乎不占有资源,他们共享进程里的资源,内核调度起来不会那么像进程切换那么耗费资源。 总结:线程是用来进行内核调度进行共享资源,系统执行。 协程:协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此,协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作执行者则是用户自身程序,goroutine也是协程。 总结:拥有属于自己的寄存器,在切换回来时恢复保存在上下文的栈,用户自身程序执行。 区别:线程和协同程序的主要不同在于:在多处理器情况下,从概念上来讲多线程程序同时运行多个线程; 而协同程序是通过协作来完成,在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只在必要时才会被挂起
创建goroutine 1 2 3 4 5 6 7 8 9 10 11 12 13 14 func NewTest () { for { fmt.Println("这是一个新任务" ) time.Sleep(time.Second) } } func main () { go NewTest() for { fmt.Println("这是一个主协程" ) time.Sleep(time.Second) } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 func main () { i:=0 go func () { for { i++ fmt.Println("子协程 i= " ,i) time.Sleep(time.Second) if i == 10 { break } } }() j := 0 for { j++ fmt.Println("main j= " ,j) time.Sleep(time.Second) if j == 2 { break } } }
runtime包 Gosched 用于让出CPU时间片,让出当前goroutine的执行权限,调度器安排其他等待的任务运行,并在下次某个时候从该位置恢复执行
1 2 3 4 5 6 7 8 9 10 11 12 13 func main () { go func () { for i:=0 ;i<5 ;i++{ fmt.Println("子协程" ) } }() for i:=0 ;i<2 ;i++{ runtime.Gosched() fmt.Println("main" ) } }
Goexit 立即终止当前协程执行,调度器确保所有已注册的defer延迟调度被执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func test () { defer fmt.Println("world" ) runtime.Goexit() fmt.Println("hello world" ) } func main () { go func () { fmt.Println("子协程" ) test() fmt.Println("hello" ) }() for { } }
GOMAXPROCS 用来设置可以并发计算的CPU核数的最大值,并返回之前的值
1 2 3 4 5 6 7 8 9 func main () { n:=runtime.GOMAXPROCS(1 ) fmt.Println(n) for { go fmt.Print(1 ) fmt.Print(0 ) } }
channel goroutine运行在相同的地址空间,因此访问共享内存必须做好同步,goroutine奉行通过通信来共享内存,而不是通过共享内存来通信。
引用channel是CSP模式的具体体现,用于多个goroutine通讯,其内部实现了同步,确保并发安全。
默认情况下,channel接收和发送数据都是阻塞的,除非是另一端已经准备好,这样就使得goroutine同步变得更加简单,而不是显式的lock
通过channel实现同步和数据交互 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 var ch = make (chan int )func Printer (str string ) { for _,data:=range str{ fmt.Printf("%c" ,data) time.Sleep(time.Second) } fmt.Println() } func person1 () { Printer("hello" ) ch <- 666 } func person2 () { <-ch Printer("world" ) } func main () { go person1() go person2() for { } }
无缓冲的channel 无缓冲的通道是指在接收前没有能力保存任何值的通道
这种类型的通道要求发送goroutine和接收goroutine同时准备好,才能完成发送和接收操作,如果两个goroutine没有同时准备好,通道会导致先执行发送或接收操作的goroutine阻塞等待
无缓冲的channel创建格式
如果没有指定缓冲区容量,那么该通道就是同步的,因此会阻塞发送者准备好发送和接收者准备好接收
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func main () { ch := make (chan int ) fmt.Printf("len(ch) = %d , cap(ch) = %d\n" ,len (ch),cap (ch)) go func () { for i:=0 ; i<3 ; i++{ fmt.Println("子协程 i = " ,i) ch <- i } }() time.Sleep(2 *time.Second) for i:=0 ; i<3 ;i++{ num := <- ch fmt.Println("num = " ,num) } }
有缓冲的channel 有缓冲的通道是一种在被接收前能存储一个或多个值的通道
它不需要发送goroutine和接收goroutine同时准备好,就完成发送和接收操作。只有在通道中没有要接收的值时接收才会阻塞,在没有缓冲区容纳被发送的值时发送才会阻塞。
有缓冲的channel创建格式
1 make (chan Type ,capacity)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func main () { ch := make (chan int , 3 ) fmt.Printf("len(ch) = %d , cap(ch) = %d\n" ,len (ch),cap (ch)) go func () { for i:=0 ; i<3 ; i++{ fmt.Println("子协程 i = " ,i) ch <- i fmt.Printf("len(ch) = %d , cap(ch) = %d\n" ,len (ch),cap (ch)) } }() time.Sleep(2 *time.Second) for i:=0 ; i<3 ;i++{ num := <- ch fmt.Println("num = " ,num) } }
注:打印的结果并不能表现为过程,因为写入内容到channel的时候可能读取内容的打印信息还没有执行完
关闭channel 1 2 var ch = make (chan Type)close (ch)
channel关闭后,不可写入,但可以继续读
单向channel 默认情况下,通道是双向的,既可以发送数据也可以接收数据。若要求只让它接收数据或发送数据,就需要指定通道方向
1 2 3 var ch chan int var ch chan <- float64 var ch <-chan int
定时器 Timer 1 2 3 4 5 6 7 8 func main () { timer := time.NewTimer(2 *time.Second) fmt.Println("当前时间" ,time.Now()) t := <- timer.C fmt.Println("t = " ,t) }
1 2 3 4 5 func main () { <-time.After(2 *time.Second) fmt.Println("时间到" ) }
1 2 3 4 func main () { time.Sleep(2 *time.Second) fmt.Println("时间到" ) }
1 2 timer.Reset(1 *time.Second)
Ticker Ticker是一个定时触发的计时器,它会以一个间隔往channel发送一个事件(当前时间),而channel的接收者可以以固定的时间间隔从channel中读取事件
1 2 3 4 5 6 7 func main () { ticker := time.NewTicker(1 * time.Second) for { t := <- ticker.C fmt.Println("time = " , t) } }
select 通过select可以监听channel的数据流动
语法与switch相似,不过select的每个case语句必须是一个IO操作,如果没有default语句,select将被阻塞,直到至少有一个通信可以进行下去
select实现fibonaacci数列 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package mainimport ( "fmt" ) func fibonacci (ch chan <- int ,quit <- chan bool ) { x,y := 1 ,1 for { select { case ch<-x: x,y = y ,x+y case flag := <-quit: fmt.Println("flag = " ,flag) return } } } func main () { ch := make (chan int ) quit := make (chan bool ) go func () { for i:=0 ; i< 8 ;i++ { num := <- ch fmt.Println("num = " ,num) } quit <- true }() fibonacci(ch,quit) }
超时 有时候会出现goroutine阻塞的情况,我们可以利用select来设置超时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func main () { c := make (chan int ) o := make (chan bool ) go func () { for { select { case i:= <- c: fmt.Println(i) case <-time.After(5 *time.Second): fmt.Println("超时了" ) o <- true return } } }() c <- 1 <- o }