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

你可能感兴趣的:(Go并发编程(goroutine、channel、锁))