Go并发编程(goroutine、channel、锁)

参考资料 https://www.jianshu.com/p/c3d65105fa46
讲channel超级详细的
https://www.jianshu.com/p/24ede9e90490
goroutine的使用 go中的goroutine是一个轻量级的线程,执行时只需要4-5k的内存,比线程更易用,更高效,更轻便,调度开销比线程小,可同时运行上千万个并发。

  1. go语言中开启一个goroutine非常简单,go函数名(),就开启了个微线程
go sayHello()

  1. Gosched()让当前正在执行的goroutine放弃CPU执行权限。调度器安排其他正在等待的线程运行。类似于java的await(),但是defer函数将会继续被调用。
for i := 0; i < 10; i++{ fmt.Print("hello ") runtime.Gosched() }

channel https://www.jianshu.com/p/24ede9e90490
  1. channel是一个先进先出的队列,多线程之间通讯的工具.
  2. channle类似于java的非基本类型的对象,当赋值或参数传递时,只是拷贝了一个 channel 引用,指向相同的 channel 对象。所以拿chan当做参数进行操作的时候,不同函数操作的是同一个对象
  3. channel的关闭
  • 关闭一个未初始化(nil) 的 channel 会产生 panic
  • 重复关闭同一个 channel 会产生 panic
  • 向一个已关闭的 channel 中发送消息会产生 panic
  • 从已关闭的 channel 读取消息不会产生 panic,且能读出 channel 中还未被读取的消息,若消息均已读出,则会读到类型的零值。从一个已关闭的 channel 中读取消息永远不会阻塞,并且会返回一个为 false 的 ok-idiom,可以用它来判断 channel 是否关闭
  • 关闭 channel 会产生一个广播机制,所有向 channel 读取消息的 goroutine 都会收到消息
//关闭一个未初始化的channel会报错 var channel chan int close(channel)//重复关闭同一个 channel 会产生 panic var channel chan int = make( chan int) close(channel) close(channel)func main(){ var channel chan int = make( chan int,10) channel<-1 channel<-2 channel<-3 close(channel)for i:= 0; i<10; i++{ x,ok:=<-channel fmt.Println(x,ok) } }

  1. channel的类型分为 有缓存channel和无缓存channel,有缓存channel类似于一个阻塞队列
//演示一个长度为1的 生效消费阻塞队列 func main(){ buf:=make(chan int) flg := make(chan int) go producer(buf) go consumer(buf, flg) <-flg //等待接受完成 }func producer(c chan int){ defer close(c) // 关闭channel for i := 0; i < 10; i++{ c <- i // 阻塞,直到数据被消费者取走后,才能发送下一条数据 fmt.Println(i,"is sended") } }func consumer(c, f chan int){ for{ if v, ok := <-c; ok{ time.Sleep(time.Microsecond*50) fmt.Println(v) // 阻塞,直到生产者放入数据后继续读取数据 }else{ break } } f<-1 //发送数据,通知main函数已接受完成 }

  1. 利用channel的阻塞模式,可以实现线程调度,例如下面的例子,实现数组完成之后的再从主线程输出
c := make(chan int)// Allocate a channel.// Start the sort in a goroutine; when it completes, signal on the channel. go func() { list.Sort() c <- 1// Send a signal; value does not matter. }() doSomethingForAWhile() <-c

  1. 支持range遍历,其实就是 取的操作,一直到取完为止
ch := make(chan int, 10) for x := range ch{ fmt.Println(x) }

  1. select类似于java NIO,多路复用,每次只能读取一个channel,如果多个channel里面都有事件,那么就随机挑选一个,如果所有channel里面都没有事件,则当前线程阻塞,直到有任意一个channel是事件,执行完一个channel事件后结束。所以持续监听的话,需要套用一个无线的for循环
func main(){ channel1 :=make(chan int,5) channel2 :=make(chan int,5) channel3 :=make(chan int,5) go producer(channel1) go producer(channel2) go producer(channel3)for{ select { case num1:= <-channel1: fmt.Println("received ", num1, " from channel1\n") case num2:= <-channel2: fmt.Println("received ", num2, " from channel3\n") case num3:= <-channel3: fmt.Println("received ", num3, " from channel3\n") } } time.Sleep(time.Second*50) }func producer(c chan int){ defer close(c) // 关闭channel for i := 0; i < 10; i++{ c <- i // 阻塞,直到数据被消费者取走后,才能发送下一条数据 fmt.Println(i,"is sended") time.Sleep(time.Second) } }

  1. 结合select和超时设置,可以实现对某一个或者某几个channel实现定时监控的功能。例如5秒内,如果所有channel都没有事件,那么就结束主线程
func main(){ ch := make(chan string) go doTask(ch) select { case <- ch: fmt.Println("task finished.") case <- time.After(5 * time.Second): fmt.Println("task timeout.") } }func doTask(channel chan string){ time.Sleep(time.Second*10) channel<-"hello world" }

  1. 函数申明的时候,可以限定该channel为单向channel,如果指向逆向的操作,会报错
func oneWayChannel(channel chan<- int){ channel<-5 age:= <-channel//报错,该channel只允许写入 fmt.Println(age) }

Go语言锁的用法 分为互斥锁(sync.Mutex)和读写锁两种锁
  1. 互斥锁的使用:多个线程操作同一把锁,实现多线程之间的管理,保证线程安全
func main(){ var mutex=new(sync.Mutex) go testLock(mutex,1) go testLock(mutex,2) time.Sleep(time.Second*20) }functestLock(mutex *sync.Mutex,num int){ mutex.Lock() defer mutex.Unlock() fmt.Println("i am going ,in thread num",num) time.Sleep(time.Second*3) fmt.Println("i am ending ,in thread num",num) }

  1. go的互斥锁不支持重入,也不支持重复unlock
  • 一个已经锁住的互斥锁不能再次被锁住,不管是同一个还是另一个goroutine
  • 一个已经释放的互斥锁也不能再次被释放,不管是同一个还是另一个goroutine
  1. 读写锁的规则,类似于mysql的读写锁,本质就是一种运行关联操作并行和不可并行的一种逻辑实现。实现了比java更灵活的锁特性
  • 可以随便读。多个goroutin同时读。执行写锁的操作的话,会进行等待直到所有的读锁都释放
  • 写的时候,啥都不能干。不能读,也不能写
sync.RWMutex 支持4个方法: Lock、Unlock (写锁)、RLock、RUnlock(读锁)
支持并发的读操作
var m *sync.RWMutex func main() { m = new(sync.RWMutex) go read(1) go read(2) time.Sleep(2 * time.Second) } func read(i int) { println(i, "read start") m.RLock() defer m.RUnlock() println(i, "reading") time.Sleep(1 * time.Second) println(i, "read end") }

【Go并发编程(goroutine、channel、锁)】支持并发读写的使用:读的操作和写的操作不能同时发生
var m *sync.RWMutex var ticketCount int func main() { m = new(sync.RWMutex) ticketCount =20go read(1) go read(2) go useTicket(3) go read(4) time.Sleep(10 * time.Second) } func read(i int) { m.RLock() defer m.RUnlock() println(i, "reading count is",ticketCount) time.Sleep(1 * time.Second) println(i, "read end") }func useTicket(threadNum int){ m.Lock() defer m.Unlock() println(threadNum, "writing start") ticketCount-- time.Sleep(1 * time.Second) println(threadNum, "writing end") }

    推荐阅读