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)
}

func  testLock(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")
}

支持并发读写的使用:读的操作和写的操作不能同时发生

var m *sync.RWMutex
var ticketCount int
func main() {
    m = new(sync.RWMutex)
    ticketCount =20

    go 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")
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容