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 = 1 for i := 1; i <= p.n; i++ { sum *= uint64(i) // i 是 int 类型, sum 是 uint 类型,要强转一下 } lock.Lock() // 加锁;修改公共数据 要加锁 m[p.n] = sum lock.Unlock() // 释放锁 } func main(){ for i:= 0; i <= 10; i++ { t := &task{i} fmt.Printf("%d addr:%p\n",i,&t) go calc(t) // 开启 11 个新线程 } time.Sleep(time.Second * 5) 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 0 addr:0xc00000e030 // 每次生成的 t 都是不同的地址 1 addr:0xc00000e040 2 addr:0xc00000e048 3 addr:0xc00000e050 4 addr:0xc00000e058 5 addr:0xc00000e060 6 addr:0xc00000e068 7 addr:0xc00000e070 8 addr:0xc00000e078 9 addr:0xc00000e080 10 addr:0xc00000e088 10! = 3628800 3! = 6 4! = 24 5! = 120 6! = 720 7! = 5040 0! = 1 1! = 1 2! = 2 8! = 40320 9! = 362880 [root@NEO example01_goroutine01_factorial]# // 第一次写上面的代码的时候,犯了如下错误: func main(){ var t task // 错误原因: 全程只生成了这一个 t for i:= 0; i <= 10; 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 只能存放整数
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,10) // 管道初始化;管道满了后就不能再往里面写了(也可以阻塞) m := make(map[string]string,16) // 声明并初始化;超过16个元素后 m 会自动扩容 m["stu01"] = "neo01" m["stu02"] = "neo02" mapChan <- m // 把 m 写入 mapChan 管道 var stuChan chan *student stuChan = make(chan *student,10) // 初始化的时候也要 chan *student stu := student{name:"neo"} stuChan <- &stu // 写入是传入地址 var interfChan chan interface{} // interface{} --> 这样 channel 中可存任何类型 interfChan = make(chan interface{},10) 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 := 0; i < 15; 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) // 管道超过10个元素后,就不能再管道中添加元素(再加就也会阻塞) go write(intChan) go read(intChan) time.Sleep(time.Second * 20) } // 运行结果: [root@NEO example03_chan_goroute]# go run main/main.go put data i:0 put data i:1 put data i:2 put data i:3 put data i:4 put data i:5 put data i:6 put data i:7 put data i:8 put data i:9 0 put data i:10 1 put data i:11 2 put data i:12 3 put data i:13 4 put data i:14 5 6 7 8 9 10 11 12 13 14 [root@NEO example03_chan_goroute]#
// 上面代码的运行现象:同时开了 read() 和 write() 两个 goroutine,write() 中的前10个元素会立马写入到 管道 中,然后 write() 会阻塞,此时 read() 每秒读取一个,write() 再写入一个