systemd是linux的初始进程,也是守护进程。systemd的功能十分复杂,从最初init所做的并行启动系统所需进程,到进程管理、日志等等功能都有,可以参考以下博客

https://blog.csdn.net/small_queen/article/details/115531530

使用systemctl enable/disable可以设定、取消一个程序的开机自启,其创建的指令在/lib/systemd/system/下(或者使用systemctl去看load行),如果某个软件安装的时候没有在这个目录下生成文件,那么必须得要手动配置后才能使用systemctl去控制开关,其格式以sshd.service为例

[Unit] #启动顺序与依赖顺序
Description=OpenSSH server daemon #描述
Documentation=man:sshd(8) man:sshd_config(5) #描述文档
After=network.target sshd-keygen.service #如果写在这行的服务需要启动,那么在此之前需要先启动sshd
Wants=sshd-keygen.service #表示两者之间是弱依赖关系,一个挂了不影响另外一个,另外有Requires字段表示强依赖

[Service]
Type=notify
EnvironmentFile=/etc/sysconfig/sshd #环境参数配置文件
ExecStart=/usr/sbin/sshd -D $OPTIONS #自定义启动进程时的命令
ExecReload=/bin/kill -HUP $MAINPID #自定义reload时的命令
KillMode=process #定义 Systemd 如何停止 sshd 服务。
Restart=on-failure #Restart字段:定义了 sshd 退出后,Systemd 的重启方式。
RestartSec=42s #退出后重启前需要等待的时间

[Install] #定义以何种方式自启
WantedBy=multi-user.target #multi-user.target - 多用户命令行


#killmode字段
#control-group(默认值):当前控制组里面的所有子进程,都会被杀掉
#process:只杀主进程
#mixed:主进程将收到 SIGTERM 信号,子进程收到 SIGKILL 信号
#none:没有进程会被杀掉,只是执行服务的 stop 命令。


#restart字段
#no(默认值):退出后不会重启
#on-success:只有正常退出时(退出状态码为0),才会重启
#on-failure:非正常退出时(退出状态码非0),包括被信号终止和超时,才会重启
#on-abnormal:只有被信号终止和超时,才会重启
#on-abort:只有在收到没有捕捉到的信号终止时,才会重启
#on-watchdog:超时退出,才会重启
#always:不管是什么退出原因,总是重启

http://t.zoukankan.com/hukey-p-11031157.html

go 的testing包提供了一个单元测试框架,当你想进行一场单元测试,你只需要在目录下创建一个以_test为结尾的go文件,导入这个包,编写一个以Test为开头的测试函数,最后再用go test来运行这个文件就行
例子:测试我在sync这一章中写的print_something函数

package main

import (
    "io"
    "os"
    "strings"
    "sync"
    "testing"
)

func Test_printSomething(t *testing.T) { //testing测试框架,函数必须以Test开头,调用go test调用
    stdOut := os.Stdout //标准输出文件描述符(type file struct)

    r, w, _ := os.Pipe() //Pipe returns a connected pair of Files; reads from r return bytes written to w. It returns the files and an error, if any.
    os.Stdout = w

    var wg sync.WaitGroup
    wg.Add(1)

    go print_something("epsilon", &wg)

    wg.Wait()

    _ = w.Close() //Close closes the File, rendering it unusable for I/O. On files that support SetDeadline, any pending I/O operations will be canceled and return immediately with an ErrClosed error. Close will return an error if it has already been called.

    result, _ := io.ReadAll(r) //func ReadAll(r Reader) ([]byte, error).ReadAll reads from r until an error or EOF and returns the data it read. A successful call returns err == nil, not err == EOF
    output := string(result)

    os.Stdout = stdOut

    if !strings.Contains(output, "epsilon") {
        t.Errorf("Expected to find epsilon, but it is not there")
    }
}


//go test .  
//ok      modulename      0.014s

http://www.jquerycn.cn/a_41217

sync.WaitGroup

我们在前面的例子里面,为了保证协程的运行,使用了time.Sleep来保证他的运行,但这是一种很不明智的做法,当我们就一个的时候我们可以写个等待一秒,但是当我们有许多个协程的时候呢?我们需要等待多久?或者说难道一个协程等一秒?不管哪个方案都是一种灾难
WaitGroup会阻塞线程直到一组协程结束,他使用add来衡量这组协程的规模,使用wait来进行阻塞直到计数器为0,使用done来减少计数器的值,因此done应在函数执行完之后再被执行。需要注意的是当add里面的计数器为0,所有被阻塞的线程都会被释放,而当小于0的时候会引发panic

package main

import (
    "fmt"
    "sync"
)

func print_something(s string, wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Println(s)
}

func main() {
    var wg sync.WaitGroup
    list := []string{
        "first",
        "second",
        "third",
        "four",
        "five",
        "six",
        "seven",
    }
    wg.Add(7)
    for i, x := range list {
        go print_something(fmt.Sprintf("%d:%s", i, x), &wg)
    }
    wg.Wait()
}

sync.Mutex

go协程的调度是由go来确定的,我们并不知道其具体的顺序,当多个协程竞争一个变量时很有可能会出现data race问题,这个时候得用互斥锁来保证最后结果的正确性
当一个协程使用lock的时候,这个被lock的资源直到他unlock释放之前,除了他其他的协程都不能动

package main

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup
var msg string

func update(s string, m *sync.Mutex) {
    defer wg.Done()

    m.Lock()
    msg = s
    m.Unlock()
}

func main() {
    msg = "init"
    var mutex sync.Mutex //mutex is a struct

    wg.Add(2)
    go update("hello,nice to see you", &mutex)
    go update("vice versa", &mutex)
    wg.Wait()
    fmt.Println(msg)
}

//go run ./example.go
//hello,nice to see you
//go run ./example.go
//hello,nice to see you
//go run ./example.go
//vice versa

让我们现在来看看如果没有锁会发生什么。。。

package main

import "testing"

func Test_update(t *testing.T) {
    msg = "nihao"

    wg.Add(2)
    go update("hello")
    go update("ohayo")
    wg.Wait()

    if msg != "ohayo" {
        t.Errorf("error value in msg")
    }
}
go test -race .
==================
WARNING: DATA RACE
Write at 0x0000012cb9e0 by goroutine 9:
  modulename.update()
      /Users/td/Documents/blog/example.go:13 +0x6f
  modulename.Test_update.func2()
      /Users/td/Documents/blog/example_test.go:10 +0x37

Previous write at 0x0000012cb9e0 by goroutine 8:
  modulename.update()
      /Users/td/Documents/blog/example.go:13 +0x6f
  modulename.Test_update.func1()
      /Users/td/Documents/blog/example_test.go:9 +0x37

Goroutine 9 (running) created at:
  modulename.Test_update()
      /Users/td/Documents/blog/example_test.go:10 +0x92
  testing.tRunner()
      /usr/local/go/src/testing/testing.go:1439 +0x213
  testing.(*T).Run.func1()
      /usr/local/go/src/testing/testing.go:1486 +0x47

Goroutine 8 (finished) created at:
  modulename.Test_update()
      /Users/td/Documents/blog/example_test.go:9 +0x86
  testing.tRunner()
      /usr/local/go/src/testing/testing.go:1439 +0x213
  testing.(*T).Run.func1()
      /usr/local/go/src/testing/testing.go:1486 +0x47
==================
--- FAIL: Test_update (0.00s)
    testing.go:1312: race detected during execution of test
FAIL
FAIL    modulename      0.021s
FAIL

dining philosophers

有五个哲学家绕着一张桌子吃面,这种面必须要两把叉子才能吃,现在每人面前有一个盘子,盘子两边各自有一个叉子,也就是说相邻的两个人不可能同时吃饭,现在设计一个程序来让这五个人都吃到饭

Trevor 的原设计方案是创建左右叉子同步锁,用循环来运行协程,除开第一个人,后面每个人的左手叉子是上一个人的右手叉子,而他的右手叉子则是一个新的锁。这样就会导致一个问题:理论上来讲最后一个人的右手叉子应该是第一个人的左手叉子,但是他这种设计方案使得最后一个的右手叉子变成了一个新的独立的锁,叉子数量实际上多了一个!

新方案:给每个叉子编号,跟人同号代表那人左手边的叉子,同时最大只允许四个人拿起左叉子

package main

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

// The Dining Philosophers problem is well known in computer science circles.
// Five philosophers, numbered from 0 through 4, live in a house where the
// table is laid for them; each philosopher has their own place at the table.
// Their only difficulty – besides those of philosophy – is that the dish
// served is a very difficult kind of spaghetti which has to be eaten with
// two forks. There are two forks next to each plate, so that presents no
// difficulty. As a consequence, however, this means that no two neighbours
// may be eating simultaneously.

// constants
const hunger = 3

// variables
var philosophers = []string{"Plato", "Socrates", "Aristotle", "Pascal", "Locke"}
var wg sync.WaitGroup
var sleepTime = 1 * time.Second
var eatTime = 3 * time.Second
var leftlocknumber = 0

func diningProblem(philosopher string, leftfork *sync.Mutex, rightfork *sync.Mutex) {
    defer wg.Done()
    fmt.Println(philosopher, "is steated")

    time.Sleep(sleepTime)

    for i := hunger; i > 0; i-- {
        fmt.Println(philosopher, "feels so hungry")
        time.Sleep(sleepTime)

        if leftlocknumber <= 4 {

            leftfork.Lock()
            leftlocknumber++
            fmt.Println(philosopher, "gets the leftfork")

            rightfork.Lock()
            fmt.Println(philosopher, "gets the rightfork")

            fmt.Println(philosopher, "gets all forks needed,then start to eat")
            time.Sleep(sleepTime)

            rightfork.Unlock()
            fmt.Println(philosopher, "puts down rightfork")

            leftfork.Unlock()
            leftlocknumber--
            fmt.Println(philosopher, "puts down leftfork")
        } else {
            i++
            fmt.Printf("too many people!%d have to wait \n", philosopher)
            time.Sleep(sleepTime)
        }
    }

    fmt.Println(philosopher, "is satisfied")
    time.Sleep(sleepTime)
    fmt.Println(philosopher, "leaves")
}

func main() {
    fmt.Println("The dining philosopher problem")
    fmt.Println("------------------------------")
    forks := make([]*sync.Mutex, 5)

    for i := 0; i < len(philosophers); i++ {
        forks[i] = &sync.Mutex{}
    }

    wg.Add(len(philosophers))

    for i := 0; i < len(philosophers); i++ {
        if i == 4 {
            go diningProblem(philosophers[i], forks[i], forks[0])
        } else {
            go diningProblem(philosophers[i], forks[i], forks[i+1])
        }
    }
    wg.Wait()

    fmt.Println("the table is empty")

}

运行结果

go run .
The dining philosopher problem
------------------------------
Locke is steated
Plato is steated
Socrates is steated
Aristotle is steated
Pascal is steated
Pascal feels so hungry
Locke feels so hungry
Plato feels so hungry
Socrates feels so hungry
Aristotle feels so hungry
Aristotle gets the leftfork
Aristotle gets the rightfork
Locke gets the leftfork
Locke gets the rightfork
Locke gets all forks needed,then start to eat
Aristotle gets all forks needed,then start to eat
Socrates gets the leftfork
Locke puts down rightfork
Locke puts down leftfork
Locke feels so hungry
Aristotle puts down rightfork
Aristotle puts down leftfork
Aristotle feels so hungry
Pascal gets the leftfork
Pascal gets the rightfork
Pascal gets all forks needed,then start to eat
Plato gets the leftfork
Socrates gets the rightfork
Socrates gets all forks needed,then start to eat
Socrates puts down rightfork
Socrates puts down leftfork
Socrates feels so hungry
Plato gets the rightfork
Plato gets all forks needed,then start to eat
Aristotle gets the leftfork
Pascal puts down rightfork
Pascal puts down leftfork
Pascal feels so hungry
Aristotle gets the rightfork
Aristotle gets all forks needed,then start to eat
Locke gets the leftfork
Aristotle puts down rightfork
Aristotle puts down leftfork
Aristotle feels so hungry
Plato puts down rightfork
Plato puts down leftfork
Plato feels so hungry
Locke gets the rightfork
Locke gets all forks needed,then start to eat
Pascal gets the leftfork
Socrates gets the leftfork
Socrates gets the rightfork
Socrates gets all forks needed,then start to eat
Socrates puts down rightfork
Socrates puts down leftfork
Socrates feels so hungry
Locke puts down rightfork
Locke puts down leftfork
Locke feels so hungry
Pascal gets the rightfork
Pascal gets all forks needed,then start to eat
Plato gets the leftfork
Plato gets the rightfork
Plato gets all forks needed,then start to eat
Aristotle gets the leftfork
Plato puts down rightfork
Plato puts down leftfork
Plato feels so hungry
Socrates gets the leftfork
Locke gets the leftfork
Locke gets the rightfork
Locke gets all forks needed,then start to eat
Pascal puts down rightfork
Pascal puts down leftfork
Pascal feels so hungry
Aristotle gets the rightfork
Aristotle gets all forks needed,then start to eat
Aristotle puts down rightfork
Aristotle puts down leftfork
Aristotle is satisfied
Socrates gets the rightfork
Socrates gets all forks needed,then start to eat
Locke puts down rightfork
Locke puts down leftfork
Locke is satisfied
Plato gets the leftfork
Pascal gets the leftfork
Pascal gets the rightfork
Pascal gets all forks needed,then start to eat
Pascal puts down rightfork
Pascal puts down leftfork
Pascal is satisfied
Aristotle leaves
Socrates puts down rightfork
Socrates puts down leftfork
Socrates is satisfied
Plato gets the rightfork
Plato gets all forks needed,then start to eat
Locke leaves
Plato puts down rightfork
Plato puts down leftfork
Plato is satisfied
Pascal leaves
Socrates leaves
Plato leaves
the table is empty

本文及并发系列博客资料来源:udemy 上《working with concurrency in go》,Trevor Sawler老师讲课非常细致易懂,感兴趣可以去支持一下他

以一个披萨店的生产者消费者模型来讲解chan的运用,英语注释为老师的注释原文

package main

import (
    "fmt"
    "math/rand"
    "time"

    "github.com/fatih/color"
)

const NumberOfPizzas = 10

var pizzasMade, pizzasFailed, total int

// Producer is a type for structs that holds two channels: one for pizzas, with all
// information for a given pizza order including whether it was made
// successfully, and another to handle end of processing (when we quit the channel)
type Producer struct {
    data chan PizzaOrder
    quit chan chan error
}

// PizzaOrder is a type for structs that describes a given pizza order. It has the order
// number, a message indicating what happened to the order, and a boolean
// indicating if the order was successfully completed.
type PizzaOrder struct {
    pizzaNumber int
    message     string
    success     bool
}

// Close is simply a method of closing the channel when we are done with it (i.e.
// something is pushed to the quit channel)
func (p *Producer) Close() error {
    ch := make(chan error)
    p.quit <- ch
    return <-ch
}

// makePizza attempts to make a pizza. We generate a random number from 1-12,
// and put in two cases where we can't make the pizza in time. Otherwise,
// we make the pizza without issue. To make things interesting, each pizza
// will take a different length of time to produce (some pizzas are harder than others).
func makePizza(pizzaNumber int) *PizzaOrder {
    pizzaNumber++
    if pizzaNumber <= NumberOfPizzas {
        delay := rand.Intn(5) + 1
        fmt.Printf("Received order #%d!\n", pizzaNumber)

        rnd := rand.Intn(12) + 1
        msg := ""
        success := false

        if rnd < 5 {
            pizzasFailed++
        } else {
            pizzasMade++
        }
        total++

        fmt.Printf("Making pizza #%d. It will take %d seconds....\n", pizzaNumber, delay)
        // delay for a bit
        time.Sleep(time.Duration(delay) * time.Second)

        if rnd <=2 {
            msg = fmt.Sprintf("*** We ran out of ingredients for pizza #%d!", pizzaNumber)
        } else if rnd <= 4 {
            msg = fmt.Sprintf("*** The cook quit while making pizza #%d!", pizzaNumber)
        } else {
            success = true
            msg = fmt.Sprintf("Pizza order #%d is ready!", pizzaNumber)
        }

        p := PizzaOrder{
            pizzaNumber: pizzaNumber,
            message: msg,
            success: success,
        }

        return &p

    }

    return &PizzaOrder{
        pizzaNumber: pizzaNumber,
    }
}

// pizzeria is a goroutine that runs in the background and
// calls makePizza to try to make one order each time it iterates through
// the for loop. It executes until it receives something on the quit
// channel. The quit channel does not receive anything until the consumer
// sends it (when the number of orders is greater than or equal to the
// constant NumberOfPizzas).
func pizzeria(pizzaMaker *Producer) {
    // keep track of which pizza we are making
    var i = 0

    // this loop will continue to execute, trying to make pizzas,
    // until the quit channel receives something.
    for {
        currentPizza := makePizza(i)
        if currentPizza != nil {
            i = currentPizza.pizzaNumber
            select {
            // we tried to make a pizza (we send something to the data channel -- a chan PizzaOrder)
            case pizzaMaker.data <- *currentPizza:

            // we want to quit, so send pizzMaker.quit to the quitChan (a chan error)
            case quitChan := <-pizzaMaker.quit:
                // close channels
                close(pizzaMaker.data)
                close(quitChan)
                return
            }
        }
    }
}

func main() {
    // seed the random number generator
    rand.Seed(time.Now().UnixNano())

    // print out a message
    color.Cyan("The Pizzeria is open for business!")
    color.Cyan("----------------------------------")

    // create a producer
    pizzaJob := &Producer{
        data: make(chan PizzaOrder),
        quit: make(chan chan error),
    }

    // run the producer in the background
    go pizzeria(pizzaJob)

    // create and run consumer
    for i := range pizzaJob.data {
        if i.pizzaNumber <= NumberOfPizzas {
            if i.success {
                color.Green(i.message)
                color.Green("Order #%d is out for delivery!", i.pizzaNumber)
            } else {
                color.Red(i.message)
                color.Red("The customer is really mad!")
            }
        } else {
            color.Cyan("Done making pizzas...")
            err := pizzaJob.Close()
            if err != nil {
                color.Red("*** Error closing channel!", err)
            }
        }
    }

    // print out the ending message
    color.Cyan("-----------------")
    color.Cyan("Done for the day.")

    color.Cyan("We made %d pizzas, but failed to make %d, with %d attempts in total.", pizzasMade, pizzasFailed, total)

    switch {
    case pizzasFailed > 9:
        color.Red("It was an awful day...")
    case pizzasFailed >= 6:
        color.Red("It was not a very good day...")
    case pizzasFailed >= 4:
        color.Yellow("It was an okay day....")
    case pizzasFailed >= 2:
        color.Yellow("It was a pretty good day!")
    default:
        color.Green("It was a great day!")
    }
}

我们从main看起,先rand.seed初始化了随机数相关的种子,因为后面要用到随机数生成函数。

    // seed the random number generator
    rand.Seed(time.Now().UnixNano())

color包是打印多彩字体用的,不用管

    // print out a message
    color.Cyan("The Pizzeria is open for business!")
    color.Cyan("----------------------------------")

创建生产者,这里用到了一个自定义的类型Producer的指针类型(&表示取地址)

    // create a producer
    pizzaJob := &Producer{
        data: make(chan PizzaOrder),
        quit: make(chan chan error),
    }

我们来看下这个类型的定义,producer包含data和quit两个变量,data是一个接收自定义的类型pizzaorder的chan,这个pizzaorder包含三个变量,分别是订单号、信息、成功判断。这个变量的作用是接收后面做的披萨的信息
quit就比较有意思了,类型为接收chan error 的chan,我搜了下好像普罗米修斯也有类似的写法,这种写法的目的是为了让我们能够决定信息所传递的位置,可以看到下面的代码给producer添加了一个closer方法。这个变量的用处是判断producer的状态

// Producer is a type for structs that holds two channels: one for pizzas, with all
// information for a given pizza order including whether it was made
// successfully, and another to handle end of processing (when we quit the channel)
type Producer struct {
    data chan PizzaOrder
    quit chan chan error
}


// PizzaOrder is a type for structs that describes a given pizza order. It has the order
// number, a message indicating what happened to the order, and a boolean
// indicating if the order was successfully completed.
type PizzaOrder struct {
    pizzaNumber int
    message     string
    success     bool
}

// Close is simply a method of closing the channel when we are done with it (i.e.
// something is pushed to the quit channel)
func (p *Producer) Close() error {
    ch := make(chan error)
    p.quit <- ch
    return <-ch
}

接下来,用协程运行pizzera,并用一个循环来获取并判断上面定义的pizzajob里面的data并根据data做出响应的输出
我们先来看pizzera,他的参数为一个pizzaMaker *Producer,定义了一个数作为订单数并调用了makepizza()
来看这个makepizza,他接收一个整数,然后根据随机数来判断一个披萨是否制作成功,调用sleep假装制作,最后返回一个包含有这个披萨信息的pizzaorder类型的指针。如果超过了设定的最大披萨数,他会返回一个只包含订单号的&pizzaorder
来继续看pizzera,他用一个currentPizza来接住makepizza返回的指针,如果返回的这个pizzaorder里面有数据,把他发给job的data里面,但是如果job.quit有消息,那么就关闭channel并退出这个函数
然后来看消费者,取job.data这个channel里面的数据,如果没有超过披萨上限,那么根据披萨的状态来打印对应的值,否则,超出上限代表着所有的披萨都做完了,那么调用job.Close(),给quit发消息,这时正在运行的协程发现这点之后就会关闭channel并退出

    // run the producer in the background
    go pizzeria(pizzaJob)

    // create and run consumer
    for i := range pizzaJob.data {
        if i.pizzaNumber <= NumberOfPizzas {
            if i.success {
                color.Green(i.message)
                color.Green("Order #%d is out for delivery!", i.pizzaNumber)
            } else {
                color.Red(i.message)
                color.Red("The customer is really mad!")
            }
        } else {
            color.Cyan("Done making pizzas...")
            err := pizzaJob.Close()
            if err != nil {
                color.Red("*** Error closing channel!", err)
            }
        }
    }


// pizzeria is a goroutine that runs in the background and
// calls makePizza to try to make one order each time it iterates through
// the for loop. It executes until it receives something on the quit
// channel. The quit channel does not receive anything until the consumer
// sends it (when the number of orders is greater than or equal to the
// constant NumberOfPizzas).
func pizzeria(pizzaMaker *Producer) {
    // keep track of which pizza we are making
    var i = 0

    // this loop will continue to execute, trying to make pizzas,
    // until the quit channel receives something.
    for {
        currentPizza := makePizza(i)
        if currentPizza != nil {
            i = currentPizza.pizzaNumber
            select {
            // we tried to make a pizza (we send something to the data channel -- a chan PizzaOrder)
            case pizzaMaker.data <- *currentPizza:

            // we want to quit, so send pizzMaker.quit to the quitChan (a chan error)
            case quitChan := <-pizzaMaker.quit:
                // close channels
                close(pizzaMaker.data)
                close(quitChan)
                return
            }
        }
    }
}

// makePizza attempts to make a pizza. We generate a random number from 1-12,
// and put in two cases where we can't make the pizza in time. Otherwise,
// we make the pizza without issue. To make things interesting, each pizza
// will take a different length of time to produce (some pizzas are harder than others).
func makePizza(pizzaNumber int) *PizzaOrder {
    pizzaNumber++
    if pizzaNumber <= NumberOfPizzas {
        delay := rand.Intn(5) + 1
        fmt.Printf("Received order #%d!\n", pizzaNumber)

        rnd := rand.Intn(12) + 1
        msg := ""
        success := false

        if rnd < 5 {
            pizzasFailed++
        } else {
            pizzasMade++
        }
        total++

        fmt.Printf("Making pizza #%d. It will take %d seconds....\n", pizzaNumber, delay)
        // delay for a bit
        time.Sleep(time.Duration(delay) * time.Second)

        if rnd <= 2 {
            msg = fmt.Sprintf("*** We ran out of ingredients for pizza #%d!", pizzaNumber)
        } else if rnd <= 4 {
            msg = fmt.Sprintf("*** The cook quit while making pizza #%d!", pizzaNumber)
        } else {
            success = true
            msg = fmt.Sprintf("Pizza order #%d is ready!", pizzaNumber)
        }

        p := PizzaOrder{
            pizzaNumber: pizzaNumber,
            message:     msg,
            success:     success,
        }

        return &p

    }

    return &PizzaOrder{
        pizzaNumber: pizzaNumber,
    }
}

整个案例,使用go协程来运行生产者,消费者用for循环监听在生产者的channel,并控制何时给生产者发送停止信号

一次运行结果如下

go run .
The Pizzeria is open for business!
----------------------------------
Received order #1!
Making pizza #1. It will take 5 seconds....
Received order #2!
Making pizza #2. It will take 2 seconds....
*** We ran out of ingredients for pizza #1!
The customer is really mad!
Received order #3!
Making pizza #3. It will take 5 seconds....
*** We ran out of ingredients for pizza #2!
The customer is really mad!
Received order #4!
Making pizza #4. It will take 1 seconds....
Pizza order #3 is ready!
Order #3 is out for delivery!
Received order #5!
Making pizza #5. It will take 1 seconds....
Pizza order #4 is ready!
Order #4 is out for delivery!
Received order #6!
Making pizza #6. It will take 3 seconds....
Pizza order #5 is ready!
Order #5 is out for delivery!
Received order #7!
Making pizza #7. It will take 5 seconds....
*** We ran out of ingredients for pizza #6!
The customer is really mad!
Received order #8!
Making pizza #8. It will take 3 seconds....
Pizza order #7 is ready!
Order #7 is out for delivery!
Received order #9!
Making pizza #9. It will take 3 seconds....
Pizza order #8 is ready!
Order #8 is out for delivery!
Received order #10!
Making pizza #10. It will take 3 seconds....
Pizza order #9 is ready!
Order #9 is out for delivery!
Pizza order #10 is ready!
Order #10 is out for delivery!
Done making pizzas...
-----------------
Done for the day.
We made 7 pizzas, but failed to make 3, with 10 attempts in total.
It was a pretty good day!

goroutines

go的协程goroutines,通过go关键字启动,由runtime负责管理
协程是比线程更加轻量级的存在,线程的创建是需要向操作系统申请的,而协程可以由程序自己本身来控制(所以也叫用户态线程),go拥有一个主线程,一个线程上可以跑多个协程(但是串行)。协程多用来做并发

avatar

协程是不会阻塞主线程的运行的,并且同一个线程内的协程共享内存(堆共享栈不共享,跟线程一样),不需要加锁,主线程结束协程跟着退出。如下例,先打印了主线程,更改了然后协程才打印出了被主线程修改了值的word,因为要保证协程的运行,所以主线程还得睡三秒来等协程运行完毕

这里有个注意点!!!!!如果,你没有做任何措施来保证一个协程能够正常的运行(如下例的主线程睡几秒),那么,很有可能出现因为主线程退出过快而导致协程还没来得及运行也跟着退出的情况

  1. 栈:栈用于维护函数调用的上下文,包括函数的参数,局部变量等等。
  2. 堆:用来容纳程序中动态分配的内存区域,当程序使用malloc和new分配的内存就来自于堆里可执行文件映像。

    package main
    
    import (
     "fmt"
     "time"
    )
    
    func main() {
     word := "HELLO"
    
     go func() {
         time.Sleep(2 * time.Second)
         fmt.Println(word)
     }()
     fmt.Println("hello")
     word = "World"
     time.Sleep(3 * time.Second)
    }
    
    
    /*
    go run ./example.go 
    hello
    World
    */

    当需要进行协程上下文切换的时候,主线程只需要交换栈空间和恢复协程的一些相关的寄存器的状态就可以实现一个用户态的线程上下文切换,没有了从用户态转换到内核态的切换成本

从头到尾理解有栈协程实现原理

channel

channel用于两个协程之间的通信,当一方向调用channel来从另外一方获取信息的时候,他就会进入堵塞状态直到获取到需要的信息,同样的,如果发送方无人接收,他也会堵塞
channel使用chan关键字

从下例可以看到,goroutine调用wait_channel函数后,因为b接收不到信息,所以进入了阻塞状态,直到第十一行给c赋了值,管道里面有东西了,才继续运作下去

package main

import (
    "fmt"
)

func main() {
    c := make(chan bool)
    go wait_channel(c, "world")
    fmt.Println("Hello")
    c <- true
    <-c
}

func wait_channel(c chan bool, s string) {
    if b := <-c; b {
        fmt.Println(s)
    }
    c <- true
}

/*
go run ./example.go
Hello
world
*/

使用buffer可以避免发送方必须等待接收方出现才能继续的问题,但是如果缓冲区被塞满了,发送方依旧会被堵塞,如果buffer是空的,接收方也会被阻塞

我们可以使用range和for来监听一个channel,当结束的时候使用closed来关闭他

package main

import (
    "fmt"
)

func main() {
    c := make(chan string)
    go hello(c, 4)
    i := 0
    for s := range c {
        fmt.Println(s, "\n", i)
        i++
    }
    v, ok := <-c
    fmt.Println("Is Channel closed?", !ok, "values", v)
}

func hello(c chan string, n int) {
    for i := 0; i <= n; i++ {
        c <- "hello!"
    }
    close(c)
}

/*
go run ./example.go
hello! 
 0
hello! 
 1
hello! 
 2
hello! 
 3
hello! 
 4
Is Channel closed? true values
*/

select

select 会去同时监听多个channel,他会一直阻塞直到有channel ready,如果同时有多个channel满足条件,他会随机选择一个
default会在别的分支都没背触发的时候选中

package main

import (
    "fmt"
    "math/rand"
    "time"
)

var list = map[string]string{
    "zhangsan":  "nihao!",
    "lisi":      "hello~",
    "yoshinobu": "nigero!!",
}

func find_p(name string, server string, c chan string) {
    time.Sleep(time.Duration(rand.Intn(10)) * time.Second)
    c <- list[name]
}

func main() {
    rand.Seed(time.Now().UnixNano())
    v1 := make(chan string)
    v2 := make(chan string)

    name := "lisi"

    go find_p(name, "v1", v1)
    go find_p(name, "v2", v2)

    select {
    case word := <-v1:
        fmt.Println("v1 say", word, "as", name)
    case word := <-v2:
        fmt.Println("v2 say", word, "as", name)
    case <-time.After(5 * time.Second):
        fmt.Println("it's too late!")
        // default:
        //     fmt.Println("well........")
    }
}

//rand.Intn生成的是伪随机,必须用rand.Seed()并传入一个变化的值如当前的时间,否则不管运行几次都是同一个值
//Duration是time里的一种类型,多种time函数都以这个为参数,
//     type Duration int64

// const (
//     minDuration Duration = -1 << 63
//     maxDuration Duration = 1<<63 - 1
// )

// Common durations. There is no definition for units of Day or larger
// to avoid confusion across daylight savings time zone transitions.
//
// To count the number of units in a Duration, divide:
//    second := time.Second
//    fmt.Print(int64(second/time.Millisecond)) // prints 1000
//
// To convert an integer number of units to a Duration, multiply:
//    seconds := 10
//    fmt.Print(time.Duration(seconds)*time.Second) // prints 10s
//
// const (
//     Nanosecond  Duration = 1
//     Microsecond          = 1000 * Nanosecond
//     Millisecond          = 1000 * Microsecond
//     Second               = 1000 * Millisecond
//     Minute               = 60 * Second
//     Hour                 = 60 * Minute
// )