Go 学习之 Goroute (五)

Goroute 原理

1、进程和线程

A:进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。

B:线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的的能独立运行的基本单位。

C:一个进程可以创建和撤销多个线程,同一个进程中的多个线程之间可以并发执行。

2、并发和并行

A:多线程程序在一个核的CPU上运行,就是并发。
B:多线程程序在多个核的CPU上运行,就是并行。

file

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

为者常成,行者常至