golang 并发相关的操作

golang 的并发
golang 的线程 和 普通的线程
  • 普通的线程
    • 系统级的线程都会有一个固定大小的栈(一般默认可能是2M),这个栈主要用来保存函数递归调用时参数和局部变量。
      固定了栈的大小导致了两个问题: 一是对于很多只需要很小的栈空间的线程来说是一个巨大的浪费, 二是对于少数需要
      巨大栈空间的线程来说又面临栈溢出的风险
  • goroutine 线程
    • 一个 goroutine 会以一个很小的栈启动(可能 2KB 或 4KB), 当遇到深度递归导致当前栈空间不足时, goroutine 会
      根据需要动态地伸缩栈的大小(最大值可达到1GB). 应为启动的代价很小, 所以我们可以启用很多 goroutine
  • 区别
    • 从上面可以看出 普通的线程是要经过内核的, 是系统层次上的; goroutine 是不经过内核的, 是代码层次上的,
      golang 运行时还包含了自己的调度器,只有在当前 goroutine 发生阻塞时才会导致调度,调度器会根据具体函数
      只保存必要的寄存器,切换的代价要比系统线程低得多。
并发编程
  • sync.Mutex Lock() Unlock()
  • sync.RWMutex RLock() RUnlock() 读写锁
  • sync/atomic 原子操作
  • sync.Once 执行一次
  • atomic.Value Store() Load()
并发的安全退出
  • 基于 select 实现的管道的超时判断:

    select {
    case v := <-in:
        fmt.Println(v)
    case <-time.After(time.Second):
        return // 超时
    }
    
    
  • 通过 selectdefault 分支实现非阻塞的管道发送或接口操作:

    
    select {
    case v := <-in:
        fmt.Println(v)
    default:
        // 没有数据
    }
    
  • 通过 select来阻止main函数退出:

    
       func main(){
    	 // do some tings
    	 select{}
       }
    
  • 当有多个管道均可操作时, select 会随机选择一个管道。基于该特性我们可以用 select
    实现一个生成随机数序列的程序

    
    func main(){   
       ch := make(chan int)
       go func(){
           for {
        	selct {
     		case ch <- 0:
    		case ch <- 1:
     	    }
           }
       }()
    
       for v := range ch {
    	    fmt.Println(v)
       }
    }
    
  • 我们通过 selectdefault分支可以很容易实现一个 Goroutine 的退出控制:

    
    func worker(channel chan bool) {
       for {
           select {
           default:
        	fmt.Println("hello")
     	    // 正常工作
     	   case <-cannel:
       	    // 退出
           }
       }
    }
    
    func main(){
        cannel := make(chan bool)
        go worker(cannel)
    
        time.Sleep(time.Second)
        cannel <- true
    }
    

    但是管道的发送操作和接收操作是一一对应的, 如果要停止多个Goroutine 那么可能需要创建同样数量的管道,
    这个代价太大了。其实我们可以通过 close 关闭一个管道来实现广播的效果,所有从关闭管道接口的操作均会收到一个零值
    和一个可选的失败标志。

    func main(){
       cancel := make(chan bool)
       for i := 0; i < 10; i++ {
    	    go worker(cancel)
       }
    
       time.Sleep(time.Second)
       close(cannel)
    }
    

    我们通过 close 来关闭 cancel 管道向多个 Goroutine 广播退出的指令. 不过这个程序依然不够稳健:
    当每个 Goroutine 收到退出指令退出时一般会进行一定的清理工作, 但是退出的清理工作并不能保证被完成,因为
    main 线程并没有等待各个工作 Goroutine 退出工作完成的机制。我们可以结合 sync.WaitGroup 来改进:

    func worker(wg *sync.WaitGroup, cannel chan bool) {
       defer wg.Done()
       
       for {
    	    select {
      	    default:
        	    fmt.Println("hello")
            case <-cannel:
            	return
            }
       }
    }
    
    func main(){
        canncel := make(chan bool)
        
        var wg sync.WaitGroup
        for i := 0; i < 10; i++ {
     	   wg.Add(1)
      	   go worker(&wg, cannel)
        }
        
        time.Sleep(time.Second)
        close(cannel)
        wg.Wait()
    }
    
goroutine 相关方法
  • runtime.GOMAXPROCS(num int) 用来设置或查询可以并发执行的 goroutine 数目, n >= 1 表示设置 GOMAXPROCS 值, 否则表示查询当前的 GOMAXPROCS 值
  • runtime.Goexit() 结束当前 goroutine 的运行
  • runtime.Gosched() 放弃当前调度执行机会, 将当前 goroutine 放到队列中等待下次被调度
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值