GO并发详解

冲天香阵透长安,满城尽带黄金甲。这篇文章主要讲述GO并发详解相关的知识,希望能为你提供帮助。
并发和并行并发:逻辑上具有处理多个任务的能力。一般并发的数量要小于CPU的数量,这些并发的任务通过间隔执行的方式来执行,这里一般是在单核CPU上面。
并行:物理上具备处理多个任务的能力。物理CPU的核数和并行的任务数相同,是并发的理想目标,理论上同一时刻内一个CPU执行一个任务。


计算机是如何实现并发的?
计算机的分时调用是并发的根本,CPU通过快速的切换作业来执行不同的作业,基本的调度单位在执行的时候可以被阻塞掉,此时就会将CPU资源让出来,等到该调度单位再次被唤醒的时候,又可以使用CPU资源,而操作系统保证了整个的调度过程。


线程,进程和协程进程:是系统资源分配的最小单位,系统是由一个个进程组成的,包括文本区,数据区和堆栈区。进程的创建和销毁都比较消耗资源和时间。进程是抢占式的争夺CPU的资源,单核CPU在同一时刻只能有一个进程在执行。
线程:是CPU调度的最小单位,线程属于进程,它共享进程的整个内存空间。多线程是不安全的,进程内的一个线程崩溃会导致整个进程崩溃。


协程:协程是属于线程的,协程的程序是在线程里面跑的。协程没有线程的上下文切换,协程的切换是程序员自己控制的。协程是原子操作的,不存在代码执行一半,被终止的情况。


【GO并发详解】进程切换分两步
1.切换页目录以使用新的地址空间
2.切换内核栈和硬件上下文。


对于linux来说,线程和进程的最大区别就在于地址空间。
对于线程切换,第1步是不需要做的,第2是进程和线程切换都要做的。


Goroutine Go语言里的一种轻量级线程—协程。
  1. 相对线程,协程的优势就在于它非常轻量级,进行上下文切换的代价非常的小。
  2. 对于一个goroutine ,每个结构体G中有一个sched的属性就是用来保存它上下文的。这样,goroutine 就可以很轻易的来回切换。
  3. 由于其上下文切换在用户态下发生,根本不必进入内核态,所以速度很快。而且只有当前goroutine 的 PC, SP等少量信息需要保存。
  4. 在Go语言中,每一个并发的执行单元为一个goroutine。


Go 语言之goroutine的调度原理
??https://mp.weixin.qq.com/s/hTgIyJN7p-wrDfLj1bP1wQ??


golang 并发 & & chanchannels 是 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 并发 & & Selectselect是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:

    推荐阅读