Go 学习之 Goroute (五)
Goroute 原理
1、进程和线程
A:进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。
B:线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的的能独立运行的基本单位。
C:一个进程可以创建和撤销多个线程,同一个进程中的多个线程之间可以并发执行。
2、并发和并行
A:多线程程序在一个核的CPU上运行,就是并发。
B:多线程程序在多个核的CPU上运行,就是并行。
3、协程和线程
协程:独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于用户级线程,这些用户级线程的调度也是自己实现的。
线程:一个线程可以跑多个协程,协程是轻量级的线程。
示例
package main
import (
"fmt"
"time"
)
func test() {
var i int
for {
fmt.Println(i)
time.Sleep(time.Second)
i++
}
}
func main() {
go test()
for {
fmt.Println("i running in main")
time.Sleep(time.Second * 10)
}
}
结果打印:
GOROOT=/usr/local/go #gosetup
GOPATH=/Users/kaiyiwang/go #gosetup
/usr/local/go/bin/go build -i -o /private/var/folders/vd/b8lh5nv90p955psf8361tznw0000gn/T/___go_build_goroute_go /Users/kaiyiwang/go/Lesson4/goroute.go #gosetup
/private/var/folders/vd/b8lh5nv90p955psf8361tznw0000gn/T/___go_build_goroute_go #gosetup
i running in main
0
1
2
3
4
5
6
7
8
9
i running in main
10
11
12
13
14
15
16
17
18
19
i running in main
20
21
22
23
24
25
26
27
28
29
i running in main
30
31
32
33
34
35
36
37
38
39
i running in main
40
41
42
43
44
45
46
47
48
Process finished with exit code 130 (interrupted by signal 2: SIGINT)
Channel
1、不同 goroutine 之间如何进行通讯?
- a、全局变量和锁同步
- b、Channel
未加锁运行:
package main
var (
m = make(map[int]uint64)
)
type task struct {
n int
}
func calc(t *task) {
var sum uint64
sum = 1
for i := 1; i < t.n; i++ {
// sum *= i // 这里需要将 i 的类型int 转为uint64,必须和sum类型保持一致
sum *= uint64(i)
}
m[t.n] = sum
}
func main() {
for i := 0; i < 1000; i++ {
t := &task{n: i}
go calc(t)
}
}
打印结果:
fatal error: concurrent map writes
goroutine 538 [running]:
runtime.throw(0x1067c35, 0x15)
/usr/local/go/src/runtime/panic.go:596 +0x95 fp=0xc4200b2f00 sp=0xc4200b2ee0
runtime.mapassign(0x1058c80, 0xc420014060, 0xc42000f160, 0x1029c38)
/usr/local/go/src/runtime/hashmap.go:499 +0x667 fp=0xc4200b2fa0 sp=0xc4200b2f00
main.calc(0xc42000f160)
/Users/kaiyiwang/go/Lesson4/goroute.go:21 +0x71 fp=0xc4200b2fd8 sp=0xc4200b2fa0
runtime.goexit()
/usr/local/go/src/runtime/asm_amd64.s:2197 +0x1 fp=0xc4200b2fe0 sp=0xc4200b2fd8
created by main.main
/Users/kaiyiwang/go/Lesson4/goroute.go:29 +0x66
goroutine 1 [runnable]:
main.main()
/Users/kaiyiwang/go/Lesson4/goroute.go:28 +0x3c
...
我们可以从打印结果看出,未加锁会出现多个线程对一个变量同时操作的过程,即锁竞争过程。
加锁:
package main
import (
"fmt"
"sync"
"time"
)
var (
m = make(map[int]uint64)
lock sync.Mutex
)
type task struct {
n int
}
func calc(t *task) {
var sum uint64
sum = 1
for i := 1; i < t.n; i++ {
// sum *= i // 这里需要将 i 的类型int 转为uint64,必须和sum类型保持一致
sum *= uint64(i)
}
// 加锁
lock.Lock()
m[t.n] = sum
lock.Unlock()
}
func main() {
for i := 0; i < 1000; i++ {
t := &task{n: i}
go calc(t)
}
time.Sleep(10 * time.Second)
lock.Lock()
for k, v := range m {
fmt.Printf("%d! = %v\n", k, v)
}
lock.Unlock()
}
打印:
64! = 1585267068834414592
175! = 0
351! = 0
452! = 0
898! = 0
124! = 0
21! = 2432902008176640000
34! = 3400198294675128320
711! = 0
756! = 0
937! = 0
Process finished with exit code 0
通过全局的数据结构来通信的,另外主线程不会等协程执行完再跑,而是同时执行的。
Channel 信道
- a. 类似unix管道(pipe)
- b. 先进先出
- c. 线程安全,多个goroutine同时访问,不需要加锁
- d. channel 是有类型的,一个整数的channel 只能存放整数
channel声明
var 变量名 chan 类型
示例:
// 多了一个关键字 chan
var test chan int
var test chan string
var test chan map[string]string
var test chan stu
var test chan *stu
channel初始化
使用make进行初始化,比如:
var test chan int
test = make(chan int, 10)
var test chan string
test = make(chan string, 10)
channel 基本操作
1、从channel读取数据
var testChan chan int
testChan = make(chan int, 10)
var a int
a = <- testChan
2、从channel写入数据
var testChan chan int
testChan = make(chan int, 10)
var a int = 10
testChan <- a
带缓冲区的channel
1、如下所示,testChan 只能放一个元素
var testChan chan int
testChan = make(chan int)
var a int
a = <- testChan
2、如下所示,testChan 是带缓冲区的chan,一次可以放 10 个元素:
var testChan chan int
testChan = make(chan int, 10)
var a int = 10
testChan <- a
示例
package main
import "fmt"
func main() {
var intChan chan int
intChan = make(chan int, 10) // 创建管道并初始化
for i := 0; i < 10; i++ {
intChan <- i // 箭头向左,表示往这个管道写数据10
}
close(intChan)
// fmt.Println(intChan)
// 管道打印出来是一个地址:0xc420074000
for {
var b int
b, ok := <- intChan // 读取管道数据,并且判断管道是否被关闭
if ok == false {
fmt.Println("Chan is close")
break
}
fmt.Println(b)
}
}
打印:
0
1
2
3
4
5
6
7
8
9
Chan is close
Channel Select
package main
import "fmt"
func main() {
var ch chan int
ch = make(chan int, 10)
for i := 0; i < 10; i++ {
ch <- i
}
for {
var b int
b = <-ch
fmt.Println(b)
}
}
打印结果:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
/Users/kaiyiwang/go/Lesson4/channel_select.go:15 +0xb6
Process finished with exit code 2
我们可以看到上边的示例会出现死锁的结果,fatal error: all goroutines are asleep - deadlock!
。
package main
import (
"fmt"
"time"
)
func main() {
var ch chan int
ch = make(chan int, 10)
ch2 := make(chan int, 10)
go func() {
for i := 0; i < 10; i++ {
ch <- i
time.Sleep(time.Second)
ch2 <- i * i
}
}()
for {
select {
case v := <-ch:
fmt.Println(v)
case v := <- ch2:
fmt.Println(v)
default:
fmt.Println("get data timeout")
time.Sleep(time.Second)
}
}
}
打印:
get data timeout
0
1
0
get data timeout
get data timeout
1
4
2
3
get data timeout
get data timeout
4
5
9
16
get data timeout
get data timeout
25
36
6
7
get data timeout
get data timeout
为者常成,行者常至
自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)