协程如何退出 一般来说,我们执行协程,需要等到协程执行完毕,才能够退出。但是,当我们想要让协程提前退出,就需要一种机制,去控制协程的退出。
以下例子使用select + channel的方式,控制协程的退出
func main() { ch := make(chan bool) var wg sync.WaitGroup go func() { defer wg.Done() // 开启looper协程 looper(ch) }() wg.Add(1) // 5秒后发送中断信号 time.Sleep(time.Second * 5) fmt.Println("Signal to goroutine exit...") ch <- true fmt.Println("Signal sent.") wg.Wait() // 等待协程完全退出 fmt.Println("Exit.") } func looper(ch <-chan bool){ for { select { case <-ch: fmt.Println("go break signal") return default: fmt.Println("looper is running...") time.Sleep(time.Second * 1) } } } 上述例子通过一个ch channel 发送信号,让协程收到信号后终止循环。
使用Context控制协程退出 上述,我们可以通过select+ channel 的方式进行控制协程的退出,但是那是单个协程的退出,当我们想要控制多个协程的退出时,也可以用定义多个channel的方法,但是这会让代码变得十分复杂。
而Context,可以让我们跟踪到每个协程,从而更好地进行限制和控制。
使用 Context 改造上述的代码:
func main() { var wg sync.WaitGroup ctx, stop := context.WithCancel(context.Background()) go func() { defer wg.Done() looper(ctx) }() wg.Add(1) time.Sleep(time.Second * 5) fmt.Println("Signal to goroutine exit...") stop() // 向context发送停止指令 fmt.Println("Signal sent.") wg.Wait() fmt.Println("Exit.") } func looper(ctx context.Context){ for { select { case <-ctx.Done(): fmt.Println("go break signal") return default: fmt.Println("looper is running...") time.Sleep(time.Second * 1) } } } 上述代码实现的功能和使用select + channel也是一样的,主要改动为:
...
在某些场景中,我们写的bash脚本需要获取命令行中指定的某些参数,用以判断某些条件,或者设置某些变量等。
getopts可以让我们通过指定 -a 1 的方式指定对应的参数名称和参数值。
一个更详细的小教程,可以看这里: getopts_tutorial 使用 可以使用 while getopts ":a:p:" opt; do... 的方式,将参数名称读取到 opt 变量中,然后,在循环体中,使用 $OPTARG 获取到具体的参数值。
一个小栗子,用于快捷创建新的 gihub pages 博客模板:
#!/usr/bin/env bash # 用于快捷创建新博客 # 命令: ./new.sh -t 标签1,标签2 -n 文章标题 -l layout -c 1 layout="post" title="new-post" now=$(date +%Y-%m-%d) can_comment="false" author="ZihuanLing" while getopts ":l:t:c:n:" opt; do case $opt in l) layout="$OPTARG" ;; t) tags="[$OPTARG]" ;; c) can_comment="true" ;; n) title=$(echo "$OPTARG" | sed "s/ /\-/g") ;; \?) echo "Invalid option -$OPTARG" >&2 exit 1 ;; esac done if [ -z $tags ]; then tags="[]" fi filename="_posts/${now}-${title}.md" read -r -d '' content << EOM --- layout: ${layout} title: "${title}" date: ${now} tags: ${tags} comments: ${can_comment} author: ${author} --- <!-- more --> EOM echo "$filename" echo "$content" echo "$content" > $filename
从 lemonchan.github.io fork了这个博客项目,感觉太棒了,设计很简洁,我需要的功能基本上都有。
感谢!
go在并发的时候,可能会出现多个协程同时访问一个资源的时候,这就出现了资源竞争。也可能出现协程还在运行,但是主程序却退出了的情况,这是缺少控制导致的。
用sync包,可以方便的控制资源的访问,也可以方便实现阻塞等待,让协程执行完毕再退出程序,或者执行下一步。
资源竞争 在同一个goroutine中,如果分配的内存没有被其他的goroutine访问,只在该goroutine中使用,则不存在资源竞争问题
如果同一块内存被多个goroutine同时访问,就会产生不知道谁先访问,也无法预料最后结果的情况,这就是资源竞争:
// 共享的资源 var sum = 0 func main(){ for i := 0; i < 100; i++ { go add(10) } // goroutine 不会阻塞下面的代码,此处Sleep一下,防止main goroutine直接退出 // 而导致未完成的goroutine也被终止 time.Sleep(time.Second * 2) fmt.Println("Sum is:", sum) } func add(i int) { sum += i } 上述例子中,sum变量为共享的资源,程序运行的过程中会发生资源竞争。
使用 go build、go run、go test 这些 Go 语言工具链提供的命令时,添加 -race 标识可以帮你检查 Go 语言代码是否存在资源竞争
同步原语 sync.Mutex 互斥锁,同一个时刻,只有一个goroutine能执行某段代码,其他协程需要等待该协程执行完毕后才能继续执行。
var ( sum int mutex sync.Mutex ) func add(i int) { mutex.Lock() // defer mutex.Unlock() // 使用defer,关闭更加优雅 sum += i mutex.Unlock() } sync.RWMutex 读写锁
...
并发,就是让程序在同一时刻做多件事情。
go语言天生自带并发属性,使得并发编程十!分!方!便!,我们只需要 go 函数名() 即可!\(^o^)/
进程和线程 进程 程序启动时,系统会为其创建一个进程
线程 是进程的执行空间,一个进程可以包含多个线程,线程被操作系统调度执行 一个程序启动,对应的进程会被创建,同时也会创建一个线程(主线程),主线程结束,整个程序也就退出了。 我们可以从主线程创建其他的子线程,这就是多线程并发
协程 Goroutine goroutine比线程更加轻盈,被Go runtime调度。 启动协程: go function()
// 这里启动了两个goroutine, 一个是用go关键字触发的,另一个是 main goroutine(主线程) func main(){ go fmt.Println("Hello goroutine.") fmt.Println("Main goroutine.") time.Sleep(time.Second) } Channel 多个goroutine之间,使用 channel进行通信
声明一个channel // 直接使用 make 创建一个channel,接受的数据类型是string ch := make(chan string) // 一个channel的操作只有两种: // - 发送,向chan中发送值: chan<- // - 接受,从chan中获取值: <-chan demo
func main(){ ch := make(chan string) go func(){ fmt.Println("Message in goroutine") ch <- "goroutine finished." // send message to channel. }() fmt.Println("Main goroutine.") v := <-ch // receive message from channel fmt.Println("Message from channel: ", v) } 在上面的示例中,我们在新启动的 goroutine 中向 chan 类型的变量 ch 发送值;在 main goroutine 中,从变量 ch 接收值;如果 ch 中没有值,则阻塞等待到 ch 中有值可以接收为止。
...