goroutine

1、进程和线程

A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位
B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位
C. 一个进程可以创建和撤消多个线程;同一个进程中的多个线程之间可以并发执行

2. 协程和线程

协程:独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于用户级线程,这些用户线线程的调度也是自己实现的 (go func() 起的就是协程)
线程:一个线程上可以跑多个协程,协程是轻量级的线程 (一个物理线程可以跑多个协程)

3. 不同 goroutine 之间进行通信

// a. 全局变量 和 锁同步
// b. Channel

打印1到10所有数的阶乘(全局变量 和 锁同步)

// 示例代码:
package main import (
"fmt"
"time"
"sync" // 修改公共数据要加锁
) var (
m = make(map[int]uint64) // 定义一个全局变量的切片,用于保存生成的阶乘结果
lock sync.Mutex // 定义一个互斥锁
) type task struct {
n int
} func calc(p *task){
var sum uint64
sum = for i := ; i <= p.n; i++ {
sum *= uint64(i) // i 是 int 类型, sum 是 uint 类型,要强转一下
} lock.Lock() // 加锁;修改公共数据 要加锁
m[p.n] = sum
lock.Unlock() // 释放锁 } func main(){
for i:= ; i <= ; i++ {
t := &task{i}
fmt.Printf("%d addr:%p\n",i,&t)
go calc(t) // 开启 11 个新线程
} time.Sleep(time.Second * ) lock.Lock() // 由于读取的公共数据,而且不知道 sleep 5 秒后所有的 goroutine 是否已经全部执行完,所以此时读取 m 也要加锁
for k,v := range m {
fmt.Printf("%d! = %v\n",k,v)
}
lock.Unlock() // 如果想知道你的程序在执行过程中有没有资源竞争的情况,可以在 build 的时候加上 --race 参数
} // 运行结果:
[root@NEO example01_goroutine01_factorial]# go run main/main.go
addr:0xc00000e030 // 每次生成的 t 都是不同的地址
addr:0xc00000e040
addr:0xc00000e048
addr:0xc00000e050
addr:0xc00000e058
addr:0xc00000e060
addr:0xc00000e068
addr:0xc00000e070
addr:0xc00000e078
addr:0xc00000e080
addr:0xc00000e088
! =
! =
! =
! =
! =
! =
! =
! =
! =
! =
! =
[root@NEO example01_goroutine01_factorial]# // 第一次写上面的代码的时候,犯了如下错误:
func main(){
var t task // 错误原因: 全程只生成了这一个 t
for i:= ; i <= ; i++ {
t.n = i // 每次修改 t.n 是都是在对同一个 t 的 n 作修改
fmt.Printf("%d addr:%p\n",i,&t)
go calc(&t) // calc(&t) 在调用 t.n 时,好多线程用的都是同一个 t 中的 n
} ...
}

channel

channel概念:

a. 类似 unix 中的管道(pipe)
b. 先进先出
c. 线程安全,多个 goroutine 同时访问,不需要加锁
d. channel 是有类型的,一个整数的 channel 只能存放整数

管道分类:

) 无缓存区管道,如下:
ch := make(chan int) // By default, sends and receives block until the other side is ready. (无缓存区管道中,只有当发送者和接收者都准备好的时候,管道才不会阻塞) ) 有缓存区管道,如下:
ch := make(chan int, )
// Sends to a buffered channel block only when the buffer is full. Receives block when the buffer is empty. (有缓存区的管道)

无缓存区管道示例:

) 错误示例:
package main func main(){
var ch chan int
ch = make(chan int) // 无缓存区管道 ch <- // 此时会阻塞,因为无缓存区管道中,只有当接收者和发送者都准备好时,才不会阻塞,但程序永远都到不了接收读数据(下面一行的代码),所以程序会阻塞在这一步,由于又是阻塞在了主线程,main函数无法继续执行以后的代码,所以会 panic
<- ch // 从管道中取出一个数据
} // 运行结果:
[root@NEO ~]# go run main.go
fatal error: all goroutines are asleep - deadlock! // 直接死锁 goroutine [chan send]:
main.main()
/root/main.go: +0x55
exit status
[root@NEO ~]# ) 正确示例:
package main import (
"fmt"
) func main(){
var ch chan int
ch = make(chan int) // 无缓存区管道 go func(){ // 新开一个 goroutine;
ch <- // 新开的子线程也会阻塞在这一步;但由于不是阻塞了主线程,程序就不会 panic
}()
a :=<- ch // 从管道中读取数据;当从管道中读取数据时,上面新开的 goroutine 就不会阻塞了
fmt.Println(a)
} // 运行结果:
[root@NEO ~]# go run main.go [root@NEO ~]#

无缓存区管道死锁参考:https://blog.csdn.net/u011328417/article/details/89473323

channel 声明

// var 变量名 chan 类型
var test chan int
var test chan string
var test chan map[string]string
var test chan stu
var test chan *stu

示例代码:

package main

import "fmt"

type student struct{
name string
} func main(){
var mapChan chan map[string]string // 声明一个 channel;chan + 变量类型 才是管道的类型
mapChan = make(chan map[string]string,) // 管道初始化;管道满了后就不能再往里面写了(也可以阻塞)
m := make(map[string]string,) // 声明并初始化;超过16个元素后 m 会自动扩容
m["stu01"] = "neo01"
m["stu02"] = "neo02"
mapChan <- m // 把 m 写入 mapChan 管道 var stuChan chan *student
stuChan = make(chan *student,) // 初始化的时候也要 chan *student
stu := student{name:"neo"}
stuChan <- &stu // 写入是传入地址 var interfChan chan interface{} // interface{} --> 这样 channel 中可存任何类型
interfChan = make(chan interface{},)
interfChan <- stu /*
stu01 :=<- interfChan // 读取 interfChan;声明并初始化 stu01
fmt.Printf("stu01:%v\n",stu01)
*/
var stu01 interface{} // stu01 是一个接口类型
stu01 =<- interfChan stu02,ok := stu01.(student) // 断言
if !ok {
fmt.Println("can not convert")
return
}
fmt.Println("stu02:",stu02) } // 运行结果:
[root@NEO example02_channel01]# go run main/main.go
stu02: {neo}
[root@NEO example02_channel01]#

channel 和 goroutine 相结合的事例(一个goroutine写,一个goroutine读)

// 示例代码:
package main import (
"fmt"
"time"
) func write(ch chan int){ // 管道 chan 后面一定要加上变量类型
for i := ; i < ; i++{
ch <- i
fmt.Printf("put data i:%d\n",i)
}
} func read(ch chan int){
for {
var b int
b =<- ch
fmt.Println(b)
time.Sleep(time.Second)
}
} func main() {
var intChan chan int
intChan = make(chan int,) // 管道超过10个元素后,就不能再管道中添加元素(再加就也会阻塞) go write(intChan)
go read(intChan) time.Sleep(time.Second * )
} // 运行结果:
[root@NEO example03_chan_goroute]# go run main/main.go
put data i:
put data i:
put data i:
put data i:
put data i:
put data i:
put data i:
put data i:
put data i:
put data i: put data i: put data i: put data i: put data i: put data i: [root@NEO example03_chan_goroute]#
// 上面代码的运行现象:同时开了 read() 和 write() 两个 goroutine,write() 中的前10个元素会立马写入到 管道 中,然后 write() 会阻塞,此时 read() 每秒读取一个,write() 再写入一个

channels 的关闭  

// 示例代码:
package main import (
"fmt"
) func main(){
var ch chan int
ch = make(chan int,) for i := ; i < ; i++ {
ch <- i
} close(ch) // 关闭管道;管道关闭后就不能再往管道里面写了,但是可以从管道里面读 for {
var b int
b,ok :=<- ch // ok 等于 false 表示这个管道已经关了 (将管道中的数据读取完后,才会返回false)
if ok == false{
fmt.Println("channel is closed")
break
}
fmt.Println("ok:",ok)
fmt.Println(b)
}
} // 运行结果:
[root@NEO example03_chan_close]# go run main/main.go
ok: true ok: true ok: true ok: true ok: true ok: true ok: true ok: true ok: true ok: true channel is closed
[root@NEO example03_chan_close]# // 关闭管道的相关官方解释:
// The loop for i := range c receives values from the channel repeatedly until it is closed.
// Channels aren't like files; you don't usually need to close them. Closing is only necessary when the receiver must be told there are no more values coming, such as to terminate a range loop.

关闭管道的参考链接:https://blog.csdn.net/Tovids/article/details/77867284

利用 for range 取出管道中所有的元素

// 示例代码:
package main import "fmt" func main(){
var ch chan int
ch = make(chan int,)
for i := ; i < ; i++ {
ch <- i
} close(ch) // 把管道关闭,当 for range 取出管道中所有元素后就会自动退出 for range 循环
for v := range ch{ // 利用 for range 取出管道中所有元素
fmt.Println(v)
}
} // 运行结果:
[root@NEO example03_goroute_range]# go run main/main.go [root@NEO example03_goroute_range]#

chan 之间的同步

示例1:打印出1000以内的所有素数

// 示例代码:
package main import (
"fmt"
) func calc(src chan int, res chan int, exitch chan bool){ // 检查是否为素数的函数
for {
v,ok :=<- src // 从集合管道中取出一个元素
if ok == false { // 只有 src(intChan)close() 掉后,当 src(intChan)中所有元素取完后,ok 才会变成 false,所以 intChan 一定要关掉
break
} flag := true // 是否为素数的标识符
for i := ; i < v; i++{
if v % i == {
flag = false
break
}
} // 如果 flag == true,则为素数,添加到 res 管道中
if flag {
res <- v
}
}
exitch <- true // 线程执行完后,把 true 加到 exitch 中
} func closeResChan(exitch chan bool,res chan int){
for i := ; i <; i++{ // 如果 exitChan 管道中能取出8个元素,表示 calc() 那8个线程都已经执行完
<- exitch // 从 exitch 管道中取出一个元素(扔掉)
}
close(res) // 关掉 resChan
} func main(){
var intChan chan int
intChan = make(chan int,)
var resChan chan int
resChan = make(chan int,) // 用于存放所有的素数
var exitChan chan bool
exitChan = make(chan bool,) // 定义一个 exitChan 管道,用于判断上面的8个线程是否已经全部执行完 for i := ; i <= ; i++{ // 1000以内的数放到 intChan 管道中
intChan <- i
}
close(intChan) // 关闭管道 // 把素数添加到 resChan 管道中
for i := ; i < ; i++ { // 假设电脑为8核,开启8个线程
go calc(intChan, resChan, exitChan)
} // 当上面新开的8个线程都执行完后,才应该把 resChan close() 掉
go closeResChan(exitChan, resChan) // 从 resChan 管道中读取结果
for v := range resChan{ // 为了不让 main 线程出现死锁,则应该先把 resChan close() 掉
fmt.Println(v)
} } // for v := range channel {} // channel 管道也能 for range 来获取管道中的元素 ,v 表示取出来的 channel 中的元素

示例2:一个线程发送数据,一个线程接收数据

// 示例代码:
package main import "fmt" func send(intChan chan int, exitChan chan bool){
for i := ; i < ; i++{
intChan <- i
}
close(intChan) // 写入完成之后,关闭 intChan,这样 for range intChan 时取出数据后 才会自动退出
exitChan <- true // 表示 send() 线程已经执行完毕
} func recv(intChan chan int, exitChan chan bool){
for v := range intChan{ // 由于 send() 中已经关闭了 intChan,所以当 intChan 中的数据取完后,该 for range 会自动退出
fmt.Println(v)
} exitChan <- true
} func main(){
var intChan chan int
intChan = make(chan int,)
var exitChan chan bool
exitChan = make(chan bool,) // 用于标识程序退出;长度 2 表示最多标识2个线程 go send(intChan, exitChan)
go recv(intChan, exitChan) for i := ; i < ; i++ {
<- exitChan
}
} // 运行结果:
[root@NEO example03_goroute_sync02]# go run main/main.go [root@NEO example03_goroute_sync02]#

chan 的只读和只写

a. 只读 chan 的声明
var 变量名字 <-chan int
var readChan <-chan int b. 只写 chan 的声明
var 变量名字 chan<- int
var writeChan chan<- int

channels 的 select 操作

select 用于多个channel监听并收发消息,当任何一个case满足条件则会执行,若没有可执行的case,就会执行default,如果没有default,程序就会阻塞。
// 语法示例:
select {
case i := <-c:
// use i
default:
// receiving from c would block
}

示例代码:

package main

import (
"fmt"
"time"
) func main(){
var ch01 chan int
ch01 = make(chan int,)
ch02 := make(chan int,) go func(){
var i int
for {
ch01 <- i
time.Sleep(time.Second)
ch02 <- i * i
time.Sleep(time.Second)
i++
}
}() for {
select { // 哪个管道里面有数据,就走哪个 case;所有管道都没数据,就走 default
case v :=<- ch01:
fmt.Println("ch01 ele:",v)
case v :=<- ch02:
fmt.Println("ch02 ele:",v)
default:
fmt.Println("get data timeout")
time.Sleep(time.Second)
}
}
} // 运行结果:
[root@NEO example03_goroute_select]# go run main/main.go
get data timeout
ch01 ele:
ch02 ele:
get data timeout
get data timeout
ch01 ele:
ch02 ele:
get data timeout
get data timeout
ch02 ele:
ch01 ele:
get data timeout
get data timeout
ch01 ele:
ch02 ele:
get data timeout
get data timeout
ch01 ele:
ch02 ele:
get data timeout
^Csignal: interrupt
[root@NEO example03_goroute_select]#

定时器

// 语法示例1: NewTicker()
t := time.NewTicker(time.Second) // NewTicker() 中的参数是执行的时间间隔(多久触发一次) --> 可作超时控制(推荐)
for v := range t.C { // t.C 是一个 channel,背后也是个 goroutine
fmt.Println("hello,",v)
}
// time.NewTicker() 用完之后要关掉 --> t.Stop() // 示例2:time.After()
select {
case <- time.After(time.Second): // 一秒之后会执行一下,然后以后就不会再触发 --> 可用于设置超时时间控制
fmt.Println("after")
}
// 系统会回收 time.After() 的资源(性能不如 time.NewTicker() 的 t.Stop() 好) // 推荐使用 time.NewTicker()

time.NewTicker() 示例:

// 示例代码1:
package main import (
"fmt"
"time"
) func main(){
t := time.NewTicker(time.Second)
for v := range t.C {
fmt.Println("hello,",v)
}
} // 运行结果:
[root@NEO example04_time_newticker01]# go run main/main.go
hello, -- ::37.940256992 + CST m=+1.000517671
hello, -- ::38.940289529 + CST m=+2.000550209
hello, -- ::39.940339135 + CST m=+3.000599804
hello, -- ::40.940363886 + CST m=+4.000624567
hello, -- ::41.940399863 + CST m=+5.000660552
^Csignal: interrupt
[root@NEO example04_time_newticker01]# // 示例代码2:
package main import (
"fmt"
"time"
) func main(){
var ch01 chan int
ch01 = make(chan int,)
ch02 := make(chan int,) t := time.NewTicker(time.Second) select {
case v :=<- ch01:
fmt.Println("ch01 ele:",v)
case v :=<- ch02:
fmt.Println("ch02 ele:",v)
case <- t.C: // 超时控制
fmt.Println("get data timeout")
}
t.Stop() // 关闭 定时器
} // 运行结果:
[root@NEO example04_time_newticker02]# go run main/main.go
get data timeout // 1秒后输出了这条结果
[root@NEO example04_time_newticker02]#

time.After() 示例:

// 示例代码:
package main import (
"fmt"
"time"
) func main(){
var ch01 chan int
ch01 = make(chan int,)
ch02 := make(chan int,) select {
case v :=<- ch01:
fmt.Println("ch01 ele:",v)
case v :=<- ch02:
fmt.Println("ch02 ele:",v)
case <- time.After(time.Second): // 设置超时时间控制
fmt.Println("get data timeout")
}
} // 运行结果:
[root@NEO main]# go run main.go
get data timeout // 1秒后输出了这条结果
[root@NEO main]#

goroutine 的 recover

// 如果 goroutine 中有 panic ,那整个程序也会崩溃,如下:
// 示例代码:
package main import (
//"fmt"
"time"
) func test(){
var m map[string]string // 只声明未初始化,直接使用会 panic
m["name"] = "stu01" // goroutine 会 panic
} func main(){
for i := ; i < ; i++{
go test()
}
time.Sleep(time.Second * )
} // 运行结果:
[root@NEO example05_goroutine_panic]# go run main/main.go
panic: assignment to entry in nil map goroutine [running]:
main.test()
/root/go/project/src/go_dev/day08/example05_goroutine_panic/main/main.go: +0x4b
created by main.main
/root/go/project/src/go_dev/day08/example05_goroutine_panic/main/main.go: +0x3e
exit status
[root@NEO example05_goroutine_panic]#

异常捕获: recover()

// 示例代码:
package main import (
"fmt"
"time"
) func test(){ defer func(){ // 进行异常捕获的匿名函数
err := recover() // recover() --> 捕获异常
if err != nil {
fmt.Println("panic:",err)
}
}() var m map[string]string // 只声明未初始化,直接使用会 panic
m["name"] = "stu01"
} func main(){
for i := ; i < ; i++{
go test()
}
time.Sleep(time.Second * )
} // 运行结果:
[root@NEO example05_goroutine_panic]# go run main/main.go
panic: assignment to entry in nil map // 虽然 goroutine 中有异常,但由于做了异常捕获,主线程不会挂掉 (主线程和其他 goroutine 不受影响)
panic: assignment to entry in nil map
panic: assignment to entry in nil map
panic: assignment to entry in nil map
panic: assignment to entry in nil map
panic: assignment to entry in nil map
panic: assignment to entry in nil map
panic: assignment to entry in nil map
panic: assignment to entry in nil map
panic: assignment to entry in nil map
[root@NEO example05_goroutine_panic]#

单元测试

. 文件名必须以 _test.go 结尾
. 使用 go test 命令进行测试;
. _test.go 文件中的测试函数必须以 Test 开头
. 传入的参数 *testing.T

golang(8):channel读写 & goroutine 通信的更多相关文章

  1. 一个Golang例子:for + goroutine + channel

    Rob Pike 在 Google I/O 2012 - Go Concurrency Patterns 里演示了一个例子(daisy chain). 视频地址:https://www.youtube ...

  2. golang 部分理解:关于channel 和 goroutine 例子

    部分理解:关于channel 和 goroutine 例子package main import "strconv" import "fmt" func mai ...

  3. golang的Channel

    golang的Channel Channel 是 golang 一个非常重要的概念,如果你是刚开始使用 golang 的开发者,你可能还没有真正接触这一概念,本篇我们将分析 golang 的Chann ...

  4. 深入学习golang(2)—channel

    Channel 1. 概述 “网络,并发”是Go语言的两大feature.Go语言号称“互联网的C语言”,与使用传统的C语言相比,写一个Server所使用的代码更少,也更简单.写一个Server除了网 ...

  5. golang 中 channel 的非阻塞访问方法

    在golang中,基本的channel读写操作都是阻塞的,如果你想要非阻塞的,可以使用如下示例: 即只要在select中加入default,阻塞立即变成非阻塞: package main import ...

  6. Golang 入门 : channel(通道)

    笔者在<Golang 入门 : 竞争条件>一文中介绍了 Golang 并发编程中需要面对的竞争条件.本文我们就介绍如何使用 Golang 提供的 channel(通道) 消除竞争条件. C ...

  7. golang的channel实现

    golang的channel实现位于src/runtime/chan.go文件.golang中的channel对应的结构是: // Invariants: // At least one of c.s ...

  8. golang 中 channel 的详细使用、使用注意事项及死锁分析

    目录 1.什么是 channel,介绍管道 2.channel 的基本使用 3.channel 的使用场景 4.使用 channel的注意事项及死锁分析 什么是 channel 管道 它是一个数据管道 ...

  9. golang 学习 ---- channel

    把一个loop放在一个goroutine里跑,我们可以使用关键字go来定义并启动一个goroutine: package main import "fmt" func loop() ...

随机推荐

  1. Google 插件的使用

    每次看英文网页或者文档的时候总会碰到不认识单词,就想能不能选中单词就可以显示翻译?于是就安装Google浏览器的翻译插件,总的来说,蛮繁琐的. 1.先安装谷歌访问助手 (1.)直接百度谷歌访问助手 ( ...

  2. EBS登录问题小结

    1 网络问题 1)ping IP 2)telnet IP 端口 备注:如果端口能访问则直接跳转,如果不能访问则报错如下所示: 2)配置host文件 如果访问的服务器在内网,则需要配置host信息 3) ...

  3. Linux系统Docker配置阿里云镜像加速器

    vim /etc/docker/daemon.json # 替换为 "registry-mirrors": ["https://v2ltjwbg.mirror.aliyu ...

  4. Ubuntu16.04.3安装并配置samba方法

    目的:单个Linux用户可以在windows电脑上通过映射网络驱动器的方式来访问自己的工作目录. 方法如下: 1.安装smb服务器:apt-get install samba2.安装smb支持的文件系 ...

  5. Android四层体系架构

    Application应用层 应用是用Java语言编写的运行在虚拟机上的程序,即图中最上层的蓝色部分.手机的上层应用其实,Google最开始时就在Android系统中捆绑了一些核心应用比如e-mail ...

  6. AIX系统命令

    当/oracle达到一定使用量时需清除*.trc文件find . -name "*.trc"|xargs rm -rf 1.查看是否开启归档日志切换用户:su - oracle使用 ...

  7. Python实现的寻找前5个默尼森数算法示例

    Python实现的寻找前5个默尼森数算法示例 本文实例讲述了Python实现的寻找前5个默尼森数算法.分享给大家供大家参考,具体如下: 找前5个默尼森数. 若P是素数且M也是素数,并且满足等式M=2* ...

  8. Linux添加日常任务监控文件或日志大小

    1.使用命令"vi/vim xxx.sh"编写shell脚本文件 #!/bin/bash #author yangli # #设置文件检测路径 file_check_path=&q ...

  9. C++学习笔记-继承中的构造与析构

    C++存在构造函数与析构函数,继承中也存在构造和析构函数.继承中的构造和析构函数与普通的构造析构有细微差别. 赋值兼容性原则 #include "iostream" using n ...

  10. SpringCloud学习(四)断路器(Hystrix)(Finchley版本)

    在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以相互调用(RPC),在Spring Cloud可以用RestTemplate+Ribbon和Feign来调用.为了保证其高可用,单个服务 ...