Go并发编程(goroutine、channel、锁)
参考资料
https://www.jianshu.com/p/c3d65105fa46
讲channel超级详细的
https://www.jianshu.com/p/24ede9e90490
goroutine的使用
go中的goroutine是一个轻量级的线程,执行时只需要4-5k的内存,比线程更易用,更高效,更轻便,调度开销比线程小,可同时运行上千万个并发。
- go语言中开启一个goroutine非常简单,go函数名(),就开启了个微线程
go sayHello()
- Gosched()让当前正在执行的goroutine放弃CPU执行权限。调度器安排其他正在等待的线程运行。类似于java的await(),但是defer函数将会继续被调用。
for i := 0;
i < 10;
i++{
fmt.Print("hello ")
runtime.Gosched()
}
channel https://www.jianshu.com/p/24ede9e90490
- channel是一个先进先出的队列,多线程之间通讯的工具.
- channle类似于java的非基本类型的对象,当赋值或参数传递时,只是拷贝了一个 channel 引用,指向相同的 channel 对象。所以拿chan当做参数进行操作的时候,不同函数操作的是同一个对象
- 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)
}
}
- 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函数已接受完成
}
- 利用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
- 支持range遍历,其实就是 取的操作,一直到取完为止
ch := make(chan int, 10)
for x := range ch{
fmt.Println(x)
}
- 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)
}
}
- 结合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"
}
- 函数申明的时候,可以限定该channel为单向channel,如果指向逆向的操作,会报错
func oneWayChannel(channel chan<- int){
channel<-5
age:= <-channel//报错,该channel只允许写入
fmt.Println(age)
}
Go语言锁的用法 分为互斥锁(sync.Mutex)和读写锁两种锁
- 互斥锁的使用:多个线程操作同一把锁,实现多线程之间的管理,保证线程安全
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)
}
- go的互斥锁不支持重入,也不支持重复unlock
- 一个已经锁住的互斥锁不能再次被锁住,不管是同一个还是另一个goroutine
- 一个已经释放的互斥锁也不能再次被释放,不管是同一个还是另一个goroutine
- 读写锁的规则,类似于mysql的读写锁,本质就是一种运行关联操作并行和不可并行的一种逻辑实现。实现了比java更灵活的锁特性
- 可以随便读。多个goroutin同时读。执行写锁的操作的话,会进行等待直到所有的读锁都释放
- 写的时候,啥都不能干。不能读,也不能写
支持并发的读操作
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")
}
推荐阅读
- python青少年编程比赛_第十一届蓝桥杯大赛青少年创意编程组比赛细则
- HTML基础--基本概念--跟着李南江学编程
- 我的软件测试开发工程师书单
- 芯灵思SinlinxA33开发板Linux内核定时器编程
- 关于响应式编程的十个问题
- iOS-Swift-map|iOS-Swift-map filter reduce、函数式编程
- 零基础学习Python作业本(13)
- 网络编程基础--HTTP
- 《Unix网络编程》第一卷第三版|《Unix网络编程》第一卷第三版 源码编译
- 【读书笔记】JavaScript|【读书笔记】JavaScript DOM编程艺术 (第2版)