Go语言编程第四章并发编程

本文介绍Go语言中的并发编程概念,包括goroutine的基础使用、channel的数据传递机制及其实现细节、同步锁的应用,以及如何通过Once类型确保全局唯一性操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考书:Go语言编程

第四章  并发编程

 

1 Go语言支持协程goroutine

补充:

1.1 如果在一个函数调用前加上go关键字,即在新的goroutine中并发执行。如果这个函数有返回值,这个返回值会被丢弃

1.2 当main( )执行完即程序退出,不会等待goroutine执行完毕。

2 协程的简单例子

var counter int = 0

func Count(lock *sync.Mutex){
   lock.Lock()      // 加锁
   counter ++
   fmt.Println(counter)
   lock.Unlock()    // 解锁
}


func main(){
   lock := &sync.Mutex{}
   for i := 0;i<10;i++{
      go Count(lock)
   }
   for{
      lock.Lock()
      c := counter
      lock.Unlock()
      runtime.Gosched()  // 让出CPU时间片
      if c >= 10{
         break
      }
   }
}

3 Go语言的通信方式channel,channel可以在两个或多个goroutine之间传递消息。

4 channel是类型相关的,一个channel只能传递一种类型的值,这个类型需要在声明channel时指定

5 channel的一般声明形式为var chanName chan  ElementType,ElementType为元素类型,如下

        var  ch  chan  int

        var  m   map[string] chan  bool  // 这个是声明一个map,元素是bool型的channel

6 channel的定义也可使用make( ),如下

        ch :=  make(chan int)      // 声明并初始化了一个int类型的名为ch的channel

7 channel写入和读出。向channel写入数据通常会导致程序阻塞,直到有其他goroutine从这个channel中读取数据。如果channel之前没有写入数据,那么从channel读取数据也会导致程序阻塞,直到channel被写入数据。

        ch  <-  value       // 将value值写入ch

        value  :=   <-  ch     // 读取ch值赋值给value

8 select语句,用于监听IO操作,当IO操作执行时会触发。每个case语句都必须是一个面向channel的操作,如下

        select {

            case <- chan1 : 

                    // 如果chan1成功读到数据则进行该case处理语句,如果失败则继续执行下一个case

            case chan2 <- 2 :

                    // 如果成功向chan2写入数据则进行该case处理语句

            default :

                    // 如果上面都没有执行成功则进入default处理流程

        }

9 select语句的语法

 

  • 每个case都必须是一个通信
  • 如果有多个case都可以运行,select会随机选择一个执行,其他的不会执行
  • 如果没有case可执行,则去执行default,如果没有default,则会阻塞,直到有个case可以执行

10 创建一个带缓冲的channel

          c :=  make(chan int , 1024)    // 创建了一个大小为1024的int类型channel,即使没有读取方,写入方也可以一直往channel里写入,在缓冲区被填完之前都不会阻塞

           for  i := range c {          // 可以直接使用range关键字从channel读取,而不需要用 <- 

                        fmt.Println("Received: ", i ) 

            }

11 channel的超时机制,常见的是利用select机制,例如

timeout := make(chan bool,1)
go func(){
   time.Sleep(1e9)
   timeout <- true
}()
select {
case <- ch:
   fmt.Println("ch")
case <- timeout:
   fmt.Println("timeout")
}

补充:channel的传递例子

type PipeData struct {
    value int
    handler func(int) int
    next chan int
}

func handle(queue chan *PipeData){
    for data := range queue {
        data.next <- data.handler(data.value)
    }
}

12 channel是一个原生类型,不仅支持被channel传递,也支持类型转换。单向channel只能用于发送或接收数据。

        var  ch1 chan  int       // 不是单向channel

        var  ch2 chan<-  float64    // 单向channel,只用于写float64数据

        var  ch3 <-chan  int          //  单向channel,只用于读取int数据

        ch4 :=  make(chan int)

        ch5 := <-chan int(ch4)      // 类型转换,强制转换为一个单向的读取channel

        ch6 := chan<- int(ch4)      //  类型转换,强制转换为一个单向的写入channel

        ch7 := make(chan<- int)    // 创建单向channel

        ch8 := make(<-chan int)    // 创建单向channel

        ch9 := chan int(ch8)    //  errror , 不能将单向强制转换为双向

用法如下

        func  Parse( ch <-chan  int) {     // 限制只能读取channel

                for value := range ch {

                          fmt.Println("Parsing value :" , value)

                }

        }

13 关闭channel,用内置函数close()即可,如close(ch),如果要判断一个channel是否已经关闭,可以使用多重返回值,如下

            x , ok :=  <-ch

14 同步锁,Go语言的sync包提供了两种锁类型:sync.Mutex和sync.RWMutex。Mutex是简单的锁,当goroutine获取Mutex后其他goroutine只能等这个goroutine释放了该Mutex。RWMutex是单写多读锁,在读锁占用时会阻止写,但不阻止读,其他goroutine可以获取读锁(调用RLock())。而写锁(调用Lock())会阻止其他goroutine读和写的所有操作

补充:

1 可以在每个goroutine中控制何时主动出让时间片给其他goroutine,这可以使用runtime.Goshced( ) 实现。

2 Go语言包中的sync包提供两种锁类型:sync.Mutex和sync.RWMutex。Mutex是最简单的一种锁,当一个goroutine获取后,其他goroutine只能等待它释放Mutex。RWMutex是单写多读模型。在读锁占用时,会阻止其他goroutine写但不阻止读。在写锁占用时,会阻止其他goroutine写读。

15 对于这两种锁类型,任何一个Lock()或RLock()均需要保证对应有Unlock()或RUnlock()调用与之对应,否则可能导致等待该锁的所有goroutine处于饥饿状态,甚至可能导致死锁。

        var  l   sync.Mutex

        func  foo() {

                l.Lock()

                defer l.Unlock()

                // toDo

        }

16 全局唯一性操作,即只需要运行一遍代码,如全局初始化操作,Go语言提供了一个Once类型来实现

var a string
var once sync.Once

func setup(){
   a = "hello,world"
}

func doprint(){
   once.Do(setup)
   fmt.Println(a)
}

func twoprint(){
   go doprint()
   go doprint()
}

once的Do()可以保证在全局范围内只调用指定函数一次,即setup( ),而且其他goroutine在调用此语句时会先被阻塞,直至全局中once.Do( )执行完才能继续

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值