Go--关于 goroutine、channel

Go--关于 goroutine、channel goroutine

协程是一种轻量化的线程,由Go编译器进行优化。

Go协程具有以下特点:

有独立的栈空间

共享程序堆中的空间

调度由用户控制

如果主线程main函数(主 goroutine或者main goroutine)返回或者退出时,即使所有协程(goroutine)还没执行完毕,也会退出。当然,协程可以在主线程未退出之前自己执行完毕,并退出。

主线程是一个物理线程,直接作用在cpu上。是重量级的,非常耗费cpu资源。

协程从主线程开启的,是轻量级的线程,是逻辑态的。对资源要求相对较小。

Golang可以开启成千上万个协程。这是Golang的并发优势。

MPG模式

image-20201101201149692

image-20201101201058123

image-20201101201034227

Go1.8后,默认让程序运行在多个核上,可以不用设置了

Go1.8前,还是要设置一下,可以更高效的利益cpu

numsCPU :=runtime.NumCPU() //获取系统CPU数 runtime.GOMAXPROCS(numsCPU) //设置运行的CPU数目 channel

在此之前,先说明一种实现同步的方式:加锁(注意这里说的指互斥锁

需求:计算n!:

var lock sync.Mutex //使用全局变量加锁 func testInput(n int) { res := 1 for i := 1; i <= n; i++ { res *= i } lock.Lock() myMap[n] = uint64(res) lock.Unlock() } func main() { for i := 1; i <= 50; i++ { go testInput(i) } time.Sleep(time.Second *5) //不等待会提前结束计算,未计算的线程将被退出 lock.Lock() for i,v := range myMap { fmt.Println("map[",i,"]=",v) } lock.Unlock() }

通过加互斥锁(同步锁)的方式,并发进行运算、添加,但是这种方式也有缺点:

前面使用全局变量加锁同步来解决goroutine的通讯,但不完美

主线程在等待所有goroutine全部完成的时间很难确定,我们这里设置 5 秒,仅仅是估算。

如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能还有goroutine 处于工作状态,这时也会随主线程的退出而销毁。

通过全局变量加锁同步来实现通讯,也并不利用多个协程对全局变量的读写操作。

还可以用channel来解决:

func add(s []int , c chan int) { sum := 0 for _, v := range s { sum += v fmt.Println(v) } c <- sum } func main() { c := make(chan int) s :=[]int{2,5,9,23,7,3,4} go add(s[:len(s)/2 ] ,c) //写channel操作会阻塞,直到读channel操作执行 go add( s[len(s)/2:] ,c) x ,y:= <-c ,<-c //随机并发 fmt.Println(x ,y ,x+y) }

信道是带有类型的管道,你可以通过它用信道操作符 <- 来发送或者接收值。

ch <- v // 将 v 发送至信道 ch。 v := <-ch // 从 ch 接收值并赋予 v。

(“箭头”就是数据流的方向。)

和映射与切片一样,信道在使用前必须创建:

ch := make(chan int)

默认情况下,发送和接收操作在另一端准备好之前都会阻塞。这使得 Go 程可以在没有显式的锁或竞态变量的情况下进行同步。

channel 是线程安全的;

channel 本质是队列,遵循先进先出

channel 中只能存放指定的数据类型;

channel 的数据放满后,就不能再放入;

如果从channel 取出数据后,可以继续放入;

在没有使用协程的情况下,如果channel 数据取完了,再取,就会报deadlock。

还可以放进任意类型(interface{})的数据:

package main import "fmt" type Student struct { Name string `json:"name"` // 是 ` ` (tab键上的~按键) ,不是 ' ' Sex string `json:"sex"` } func main() { allChan := make(chan interface{},5) stu1 := Student{Name: "lili",Sex: "f"} stu2 := Student{Name: "chang",Sex: "m"} stu3 := Student{Name: "ling",Sex: "m"} allChan <- stu1 allChan <- 10 allChan <- stu2 allChan <- 99.5 allChan <- stu3 <- allChan <- allChan stuRes := <- allChan fmt.Println(stuRes) //读取结构体类型数据字段,需要先进行类型断言 stu := stuRes.(Student) fmt.Println(stu.Name) fmt.Println(stu.Sex) }

由于channel是interface{}类型,所以使用的时候,都需要先进行类型断言。

allChan <- myMap <- allChan <- allChan stuMap := <- allChan stus := stuMap.(map[int]Student) fmt.Println(stus) fmt.Println(stus[0]) fmt.Println(stus[1])

image-20201101235011061

allChan <- stu1 allChan <- 10 allChan <- stu2 allChan <- 99.5 allChan <- stu3 <- allChan n := <- allChan n += 1 //报错

image-20201102124504467

不使用类型断言,直接使用将会报错。因为编译器并不认识此类型,需要经过类型断言进行确认。

channel的关闭

channel关闭使用 close(chan),关闭channel。

关闭后不能再向channel发送数据,只能从channel读取数据。

在上面的例子中的<-allChan加入以下代码:

close(allChan)

image-20201102130028586

channel的遍历

遍历channel之前需要关闭channel,否则会报错(deadlock)。

image-20201102130608550

关闭channel后,即可正常进行遍历channel,知道遍历完成,退出遍历。

close(allChan) for v := range allChan { fmt.Println(v) } 只读、只写 channel

只读channel:(例如)

var chan1 <-chan int

只写channel:(例如)

var chan2 chan<- int

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpzgjf.html