协程
协程(Coroutine)本质上是一种用户态线程,不需要操作系统来进行抢占式调度,且在真正的实现中寄存于线程中,系统开销极小。
package main
import "fmt"
func Count(ch chan int,i int) {
fmt.Println("Counting",i)
ch <- 1
}
func main() {
chs := make([]chan int, 10)
for i := 0; i < 10; i++ {
chs[i] = make(chan int)
go Count(chs[i],i)
}
for _, ch := range(chs) {
<-ch
}
}
channel声明和定义
//声明一个传递类型为int的channel:
var ch chan int
//或者,我们声明一个map,元素是bool型的channel:
var m map[string] chan bool
//定义一个channel也很简单,直接使用内置的函数make()即可:
ch := make(chan int)
//将一个数据写入(发送)至channel的语法很直观,如下:
ch <- value
//从channel中读取数据的语法是
value := <-ch
select
通过调用select()函数来监控一系列的文件句柄,一旦其中一个文件句柄发生了IO动作,该select()调用就会被返回。
select {
case <-chan1:
// 如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1:
// 如果成功向chan2写入数据,则进行该case处理语句
default:
// 如果上面都没有成功,则进入default处理流程
}
//我们可以实现一个有趣的程序:
ch := make(chan int, 1)
for {
select {
case ch <- 0:
case ch <- 1:
}
i := <-ch
fmt.Println("Value received:", i)
}
//这个程序实现了一个随机向ch中写入一个0或者1的过程。当然,这是个死循环。
缓冲机制
要创建一个带缓冲的channel,其实也非常容易:
c := make(chan int, 1024)
在调用make()时将缓冲区大小作为第二个参数传入即可,比如上面这个例子就创建了一个大小为1024的int类型channel,即使没有读取方,写入方也可以一直往channel里写入,在缓冲区被填完之前都不会阻塞。
我们可以使用range关键来实现更为简便的循环读取:
for i := range c {
fmt.Println("Received:", i)
}
超时机制
实现超时机制:
// 首先,我们实现并执行一个匿名的超时等待函数
timeout := make(chan bool, 1)
go func() {
time.Sleep(1e9) // 等待1秒钟
timeout <- true
}()
// 然后我们把timeout这个channel利用起来
select {
case <-ch:
// 从ch中读取到数据
case <-timeout:
// 一直没有从ch中读取到数据,但从timeout中读取到了数据
}
超时机制只是使用了一个channel+延时来完成的。这种写法看起来是一个小技巧,但却是在Go语言开发中避免channel通信超时的最有效方法。
单向channel
单向channel变量的声明非常简单,如下:
var ch1 chan int // ch1是一个正常的channel,不是单向的
var ch2 chan<- float64// ch2是单向channel,只用于写float64数据
var ch3 <-chan int // ch3是单向channel,只用于读取int数据
channel是一个原生类型,因此不仅支持被传递,还支持类型转换。
单向channel和双向channel之间进行转换:
ch4 := make(chan int)
ch5 := <-chan int(ch4) // ch5就是一个单向的读取channel
ch6 := chan<- int(ch4) // ch6 是一个单向的写入channel
从设计的角度考虑,所有的代码应该都遵循“最小权限原则”,从而避免没必要地使用泛滥问题.单向channel也是起到这样的一种契约作用。
单向channel的用法:
func Parse(ch <-chan int) {
for value := range ch {
fmt.Println("Parsing value", value)
}
}
除非这个函数的实现者无耻地使用了类型转换,否则这个函数就不会因为各种原因而对ch
进行写,避免在ch中出现非期望的数据,从而很好地实践最小权限原则。
关闭channel
关闭channel非常简单,直接使用Go语言内置的close()函数即可:
close(ch)
//判断一个channel是否已经被关闭:
x, ok := <-ch
同步锁
Go语言包中的sync包提供了两种锁类型:sync.Mutex和sync.RWMutex。Mutex是最简单的一种锁类型,同时也比较暴力,当一个goroutine获得了Mutex后,其他goroutine就只能乖乖等到这个goroutine释放该Mutex。RWMutex相对友好些,是经典的单写多读模型。
对于这两种锁类型,任何一个Lock()或RLock()均需要保证对应有Unlock()或RUnlock()
典型使用模式如下:
var l sync.Mutex
func foo() {
l.Lock()
defer l.Unlock()
//...
}
全局唯一性操作
从全局的角度只需要运行一次的代码,比如全局初始化操作,Go语言提供了一个Once类型来保证全局的唯一性操作
var a string
var once sync.Once
func setup() {
a = "hello, world"
}
func doprint() {
once.Do(setup)
print(a)
}
func twoprint() {
go doprint()
go doprint()
}