goroutine与channels
goroutine(协程)
大家都知道java中的线程Thread,golang没有提供Thread的功能,但是提供了更轻量级的goroutine(协程),协程比线程更轻,创办一个协程很简单,只需要go关键字加上要运行的函数,就可以实现了。看个简单的例子:
package main import "fmt" func f(from string) {
for i := 0; i < 3; i++ {
fmt.Println(from, ":", i)
}
} func main() { f("direct") go f("goroutine") go func(msg string) {
fmt.Println(msg)
}("going") fmt.Scanln()
fmt.Println("done")
}
运行结果如下:
direct : 0
direct : 1
direct : 2
goroutine : 0
going
goroutine : 1
goroutine : 2
<enter>
done
Channels(信道)
channels是并发的goroutines之间通信的管道,我们可以从一个goroutine输出一条数据到一个channel,然后用另外一个goroutine读取,这就实现了goroutines之间的通信。下面的例子演示的是主goroutine与另一个goroutine之间的通信:
package main import "fmt" func main() { messages := make(chan string) go func() { messages <- "ping" }() msg := <-messages
fmt.Println(msg)
}
channel buffer
channel的默认空间是0,如果向一个默认长度的channel输入一条数据,程序就会阻塞,直到这条数据被读出。但是我们可以在定义channel时设置其buffer的大小,比如:
messages := make(chan string, 2)
上边这条语句定义的channel的缓存大小为2,当输入小于等于2条数据时,程序不会阻塞,但是当输入第三条数据时就会阻塞,直到被读出一条。
channel Synchronization
所谓channel Synchronization,其实就是利用channel的阻塞机制,当想让程序阻塞时,就向一个buffer为0的channel send一条数据,程序就会阻塞从而锁住,当想解锁时,就再把这个channel中的数据输出就ok了
package main import "fmt"
import "time" func worker(done chan bool) {
fmt.Print("working...")
time.Sleep(time.Second)
fmt.Println("done") done <- true
} func main() { done := make(chan bool, 1)
go worker(done) <-done
}
channel方向
所谓channel方向,其实指的就是该channel是允许信息输入,还是允许信息输出,普通的channel是既可以输入,也可以输出的,但是我们可以通过设置,让之称为单向的channel,语句如下:
只允许输出 : pings <-chan string
只允许输入 : pongs chan<- string
其实很easy,就是在定义的时候在chan关键字的前方或者后方加上“<-”即可,下面看一个相关的demo
package main import "fmt" func ping(pings chan<- string, msg string) {
pings <- msg
} func pong(pings <-chan string, pongs chan<- string) {
msg := <-pings
pongs <- msg
} func main() {
pings := make(chan string, 1)
pongs := make(chan string, 1)
ping(pings, "passed message")
pong(pings, pongs)
fmt.Println(<-pongs)
}
select
在多goroutine多channel情况下,我们不知道哪个goroutine的channel会先结束阻塞状体,我们通过select同时等待多个channel,类似于监听,然后当任何一个channel有消息传出的时候,就会类似于switch case的触发机制一样选择相应的channel读取消息,如果同时有多个消息到达,那么就会随机从一个channel读取消息,如果一直没有消息送到,select会一直阻塞。
package main import "time"
import "fmt" func main() { c1 := make(chan string)
c2 := make(chan string) go func() {
time.Sleep(1 * time.Second)
c1 <- "one"
}()
go func() {
time.Sleep(2 * time.Second)
c2 <- "two"
}() for i := 0; i < 2; i++ {
select {
case msg1 := <-c1:
fmt.Println("received", msg1)
case msg2 := <-c2:
fmt.Println("received", msg2)
}
}
}
Timeout机制
上边的select功能,如果channel种一直没有消息传出呢,那么select就会一直阻塞,在大多数情况下这不是我们的期望,我们希望有一个阈值,过了这个阈值大小我们可以让select解除阻塞,我们就可以通过timeout机制来对这种情况进行设置。
package main import "time"
import "fmt" func main() { c1 := make(chan string, 1)
go func() {
time.Sleep(2 * time.Second)
c1 <- "result 1"
}() select {
case res := <-c1:
fmt.Println(res)
case <-time.After(1 * time.Second):
fmt.Println("timeout 1")
} c2 := make(chan string, 1)
go func() {
time.Sleep(2 * time.Second)
c2 <- "result 2"
}()
select {
case res := <-c2:
fmt.Println(res)
case <-time.After(3 * time.Second):
fmt.Println("timeout 2")
}
}
Non-Blocking Channel Operations
上面我们知道了对select设置最长阻塞时间,那么现在我们设置让其不阻塞,只需要在select中添加一个default即可
package main import "fmt" func main() {
messages := make(chan string)
signals := make(chan bool) select {
case msg := <-messages:
fmt.Println("received message", msg)
default:
fmt.Println("no message received")
} msg := "hi"
select {
case messages <- msg:
fmt.Println("sent message", msg)
default:
fmt.Println("no message sent")
} select {
case msg := <-messages:
fmt.Println("received message", msg)
case sig := <-signals:
fmt.Println("received signal", sig)
default:
fmt.Println("no activity")
}
}
Closing channel
如果把一个channel close掉,那么就不能向这个channel插入数据,我们可以视为对这个channel的输入任务已经完成,代码如下:
package main import "fmt" func main() {
jobs := make(chan int, 5)
done := make(chan bool) go func() {
for {
j, more := <-jobs
if more {
fmt.Println("received job", j)
} else {
fmt.Println("received all jobs")
done <- true
return
}
}
}() for j := 1; j <= 3; j++ {
jobs <- j
fmt.Println("sent job", j)
}
close(jobs)
fmt.Println("sent all jobs") <-done
}
range over channel
在前面的例子里,我们知道,range可以对基本的数据结构进行遍历,channel也可以通过range进行遍历
package main import "fmt" func main() { queue := make(chan string, 2)
queue <- "one"
queue <- "two"
close(queue) for elem := range queue {
fmt.Println(elem)
}
}
timer and tickers
sleep功能都知道,如果我们想在将来某个时间点执行一个语句,或者每1分钟执行一次某函数,怎么办呢,timer和ticker就能完美解决需求。timer可以设置时间间隔大小,让某个语句到timer所设置的时间间隔后执行,之所以有了sleep还用timer,就是因为timer设置的时间间隔可以提前强制stop掉,如下代码:
package main import "time"
import "fmt" func main() { timer1 := time.NewTimer(2 * time.Second) <-timer1.C
fmt.Println("Timer 1 expired") timer2 := time.NewTimer(time.Second)
go func() {
<-timer2.C
fmt.Println("Timer 2 expired")
}()
stop2 := timer2.Stop()
if stop2 {
fmt.Println("Timer 2 stopped")
}
}
输出结果:
Timer 1 expired
Timer 2 stopped
因为主协程和go启动的func匿名函数相当于两个协程,所以,主协程中输出正确,但是timer2却没有正确输出,就是因为在主协程中通过timer2.stop()停止了timer2的计时等待,于是主协程先跑完了,并没有跑到另外一个协程。(ps 如果不设置等待,其他协程都不会执行,因为主协程会先跑完然后整个程序就跑完了,其他协程根本来不及跑)
tickers是设置心跳机制,先看一段代码:
ackage main import "time"
import "fmt" func main() { // Tickers use a similar mechanism to timers: a
// channel that is sent values. Here we'll use the
// `range` builtin on the channel to iterate over
// the values as they arrive every 500ms.
ticker := time.NewTicker(500 * time.Millisecond)
go func() {
for t := range ticker.C {
fmt.Println("Tick at", t)
}
}() // Tickers can be stopped like timers. Once a ticker
// is stopped it won't receive any more values on its
// channel. We'll stop ours after 1600ms.
time.Sleep(1600 * time.Millisecond)
ticker.Stop()
fmt.Println("Ticker stopped")
}
类似于一个定时器,可以这样理解:ticker.C是一个特别的channel,这里设置的是每500ms就把当前时间输入到这个channel中去,程序通过go关键字启动了一个新的协程,然后通过for循环读出时间并输出,但是读出来后channel中就没有数据了,会阻塞,知道500ms后程序又自动填充一条时间数据进去,阻塞结束,继续循环,直到主协程结束。
Worker Pools的实现
假设有3个人,同时有5份工作,给三个人分配工作随机,但有一个要求,只有完成了手头工作,才能接下一个,实现代码如下:
package main import "fmt"
import "time" func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Println("worker", id, "started job", j)
time.Sleep(time.Second)
fmt.Println("worker", id, "finished job", j)
results <- j * 2
}
} func main() { jobs := make(chan int, 100)
results := make(chan int, 100) for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
} for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs) for a := 1; a <= 5; a++ {
<-results
}
}
由于协程和线程的运行特性一样,是无序的,所以每一次的输出结果都是不同的,下面是某次输出:
worker 1 started job 1
worker 2 started job 2
worker 3 started job 3
worker 1 finished job 1
worker 1 started job 4
worker 2 finished job 2
worker 2 started job 5
worker 3 finished job 3
worker 1 finished job 4
worker 2 finished job 5
rate limit
go优雅的支持对运行程序的速度限制,通过time.Tick()和channel的阻塞特性,类似于ticker一样对程序限速,如下:
package main import "time"
import "fmt" func main() { // First we'll look at basic rate limiting. Suppose
// we want to limit our handling of incoming requests.
// We'll serve these requests off a channel of the
// same name.
requests := make(chan int, 5)
for i := 1; i <= 5; i++ {
requests <- i
}
close(requests) // This `limiter` channel will receive a value
// every 200 milliseconds. This is the regulator in
// our rate limiting scheme.
limiter := time.Tick(200 * time.Millisecond) // By blocking on a receive from the `limiter` channel
// before serving each request, we limit ourselves to
// 1 request every 200 milliseconds.
for req := range requests {
<-limiter
fmt.Println("request", req, time.Now())
} // We may want to allow short bursts of requests in
// our rate limiting scheme while preserving the
// overall rate limit. We can accomplish this by
// buffering our limiter channel. This `burstyLimiter`
// channel will allow bursts of up to 3 events.
burstyLimiter := make(chan time.Time, 3) // Fill up the channel to represent allowed bursting.
for i := 0; i < 3; i++ {
burstyLimiter <- time.Now()
} // Every 200 milliseconds we'll try to add a new
// value to `burstyLimiter`, up to its limit of 3.
go func() {
for t := range time.Tick(200 * time.Millisecond) {
burstyLimiter <- t
}
}() // Now simulate 5 more incoming requests. The first
// 3 of these will benefit from the burst capability
// of `burstyLimiter`.
burstyRequests := make(chan int, 5)
for i := 1; i <= 5; i++ {
burstyRequests <- i
}
close(burstyRequests)
for req := range burstyRequests {
<-burstyLimiter
fmt.Println("request", req, time.Now())
}
}
自此,我自学的golang的goroutine和channel的特性就介绍完了,欢迎批评指正,互相学习。
goroutine与channels的更多相关文章
- 【Go入门教程7】并发(goroutine,channels,Buffered Channels,Range和Close,Select,超时,runtime goroutine)
有人把Go比作21世纪的C语言,第一是因为Go语言设计简单,第二,21世纪最重要的就是并行程序设计,而Go从语言层面就支持了并行. goroutine goroutine是Go并行设计的核心.goro ...
- 【Go入门教程9】并发(goroutine,channels,Buffered Channels,Range和Close,Select,超时,runtime goroutine)
有人把Go比作21世纪的C语言,第一是因为Go语言设计简单,第二,21世纪最重要的就是并行程序设计,而Go从语言层面就支持了并行. goroutine goroutine是Go并行设计的核心.goro ...
- go 中goroutine 的使用
一.多线程定义: 所谓的多线程,multithreading.有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能.具有这种能力的系统包括对称多处理机.多核心处理器以 ...
- Go收藏
Go项目收藏 电子书 1.Go Web 编程 2.Go入门指南(the-way-to-go_ZH_CN) 3.Go语言圣经(中文版) Go by Example 中文 一些Go英文电子书 High P ...
- golang高级部分
一.golang之OOP(orient object programming) 在函数声明时, 在其名字之前放上一个变量, 即是一个方法. 这个附加的参数会将该函数附加到这种类型上, 即相当于为这种类 ...
- Go 收藏
Golang 定位解决分布式系统,服务器应用开发,主要竞争对手是 Java.Python 之类:Rust 定位解决单机安全问题,高性能场景偏系统底层开发,主要竞争对手就是 C 和 C++. Golan ...
- go学习笔记-并发
并发 goroutine goroutine是Go并行设计的核心.goroutine说到底其实就是协程,但是它比线程更小,十几个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现了这 ...
- go实例之轻量级线程goroutine、通道channel与select
1.goroutine线程 goroutine是一个轻量级的执行线程.假设有一个函数调用f(s),要在goroutine中调用此函数,请使用go f(s). 这个新的goroutine将与调用同时执行 ...
- 如果裸写一个goroutine pool
引言 在上文中,我说到golang的原生http server处理client的connection的时候,每个connection起一个goroutine,这是一个相当粗暴的方法.为了感受更深一点, ...
随机推荐
- axios的post请求后台(ThinkPHP5)接收不到数据
最近做vue项目,做分页的功能,使用post给后台发送数据,使用接口还是工具(postman)都可获取数据,唯独axios获取不到:经过排除,发现这与axios的post传参格式有关系: this.$ ...
- Why mobile web apps are slow
http://sealedabstract.com/rants/why-mobile-web-apps-are-slow/ I’ve had an unusual number of interest ...
- Asp.Net实现在线人数统计 (转)
原文件:http://blog.csdn.net/wxd_860825/article/details/4589292 利用Application对象和Session对象可以统计当前在线用户数量. 注 ...
- 课下实践——实现Mypwd
实现Mypwd 学习pwd命令 想要知道当前所处的目录,可以用pwd命令,该命令显示整个路径名. L 目录连接链接时,输出连接路径 P 输出物理路径 研究pwd实现需要的系统调用(man -k; gr ...
- 20155224聂小益的Linux学习
20155224聂小益的虚拟机安装 虚拟机安装 一开始,我在下载VirtulBox及Ubuntu遇到了一些困难,老实说点进去看到一大堆英文界面的时候真的是有点吓到.不过几秒钟之后就发现这并没有什么哈哈 ...
- 20155322 2016-2017-2 《Java程序设计》第2周学习总结
20155322 2016-2017-2 <Java程序设计>第2周学习总结 教材学习内容总结 本周按照教学安排学习教材的第三章,下面简单的概括一下我的学习总结: 第三章的主要内容是有关于 ...
- 20155332 2016-2017-2《Java程序设计》课程总结
20155332 2016-2017-2<Java程序设计>课程总结 1.每周作业链接汇总 2.博客之最 3.实验链接汇总 博客链接汇总 预备作业1:那些年陪伴我的老师+我期待的师生关系 ...
- 见到Unicode、GB2312、GBK 、ANSI、Ascii、DBCS、BIG5、UTF这一堆名词你是否犯晕?请看转载的好文
作者:于洋链接:https://www.zhihu.com/question/23374078/answer/69732605来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出 ...
- 【转载】OLE控件在Direct3D中的渲染方法
原文:OLE控件在Direct3D中的渲染方法 Windows上的图形绘制是基于GDI的, 而Direct3D并不是, 所以, 要在3D窗口中显示一些Windows中的控件会有很多问题 那么, 有什么 ...
- string[]转换为int[]
今天碰到一个问题,要把string[]转换为int[],但是又不想使用循环转换,找了好久最后找到了这种方法,特此记录下. string[] input = { "1", " ...