1、协程和go关键字

协程,自己管理线程,把线程控制到一定的数量,然后构造一个规则状态机来调度任务。

package main

import (
    "fmt"
    "time"
)

func Hu(){
    time.Sleep(2 * time.Second)
    fmt.Println("after 2 second hu!!!")
}

func main(){
    //开启新的协程,不会堵塞
    go Hu()

    fmt.Println("start hu,wait...")

    //必须死循环,不然主协程退出了,程序就结束了
    for{
        time.Sleep(1 * time.Second)
    }
}

结果显示:

start hu,wait...
after 2 second hu!!!

如果直接使用Hu()函数时,程序将会堵塞

然后使用go关键字开启一个新的协程,不在堵塞,在Hu()执行的时候,可以继续往下执行。

函数main作为程序的主协程,如果main函数结束的话,其它线程也会死掉,所以必须使用死循环来避免主协程终止。

2、信道chan

在两个协程之间进行通信,提供chan信道

package main

import (
    "fmt"
    "time"
)

func Hu(ch chan int){
    //使用睡眠模仿一些耗时
    time.Sleep(2 * time.Second)
    fmt.Println("after 2 second hu!!!")

    //执行语句后,通知主线程已经完成操作
    ch <- 1000
}

func main(){
    //新建一个没有缓冲的信道
    ch := make(chan int)
    //将信道传入函数,开启协程
    go Hu(ch)

    fmt.Println("start hu,wait...")

    //确保有消息的来到
    v := <-ch
    fmt.Println("receive:",v)
}

结果显示:

start hu,wait...
after 2 second hu!!!
receive: 1000

我们执行协程后,因为函数里面会睡眠两秒钟,所以两秒钟之后信道才会收到消息

在没有收到消息之前 v := <-ch 会堵塞

直到协程 go Hu(ch) 完成,那么消息收到

程序结束。

3、锁实现与并发安全

多个协程可能对同一个变量做修改操作,可能不符合预期,比如转账:
因为转账是并发的,减钱操作会读取结构体 Money 里面的 amount,同时操作时可能读到同一个值。
我们需要实现并发安全,同一时间只能允许一个协程修改金额,我们需要加锁,如下:

package main

import (
    "fmt"
    "sync"
    "time"
)

type Money struct{
    lock sync.Mutex //锁
    amount int64
}

func (m *Money) Add(i int64) {
    //加锁
    m.lock.Lock()
    //在函数执行结束后执行
    defer m.lock.Unlock()
    m.amount = m.amount + i
}

func (m *Money) Minute(i int64) {
    //加锁
    m.lock.Lock()
    //在函数执行结束后执行
    defer m.lock.Unlock()
    if m.amount >= i{
        m.amount = m.amount - i
    }
}

func (m *Money) Get() int64 {
    return m.amount
}

func main(){
    m := new(Money)
    m.Add(10000)

    for i := 0;i < 1000;i++{
        go func(){
            time.Sleep(500 * time.Millisecond)
            m.Minute(5)
        }()
    }

    time.Sleep(20 * time.Second)
    fmt.Println(m.Get())
}
5000