zoukankan      html  css  js  c++  java
  • GO编程(打卡)-Task13: 并发编程

    并发编程

    并发在图中的解释是两队人排队接咖啡,两队切换。

    并行是两个咖啡机,两队人同时接咖啡

    goroutine

    启动goroutine只要在前面调用函数前加go关键字即可

    一个goroutine必定对应一个函数,可以创建多个goroutine执行相同的函数

    通过runtime.GOMAXPROCS(n)函数设置当前程序并发时占用的CPU逻辑核心数

    并发安全和锁

    package main
    
    import (
    	"fmt"
    	"sync"
    	"time"
    )
    func worker(id int, wg *sync.WaitGroup){
    	defer wg.Done()
    	fmt.Printf("Worker %d starting
    ",id)
    	time.Sleep(time.Second)
    	fmt.Printf("Worker %d done
    ",id)
    }
    func DoSomething(doonce *sync.Once){
    	doonce.Do(func(){
    		fmt.Println("Run once - first time, loading...")
    	})
    	fmt.Println("Run this every time")
    }
    // SafeCounter 的并发使用是安全的。
    type SafeCounter struct {
    	v   map[string]int
    	mux sync.Mutex
    }
    
    // Inc 增加给定 key 的计数器的值。
    func (c *SafeCounter) Inc(key string) {
      c.mux.Lock()
      defer c.mux.Unlock()
    	// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
      c.v[key]++
    }
    
    // Value 返回给定 key 的计数器的当前值。
    func (c *SafeCounter) Value(key string) int {
    	c.mux.Lock()
    	// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
    	defer c.mux.Unlock()
    	return c.v[key]
    }
    func main() {
    	// 计数器不能为负值
    	// WaitGroup对象不是引用类型
    	var wg sync.WaitGroup
    	for i := 1; i <= 3; i++{
    		wg.Add(1)
    		go worker(i, &wg)
    	}
    	wg.Wait()
    	// sync.Once控制函数只能被调用一次,不能多次重复调用
    	var doOnce sync.Once
    	DoSomething(&doOnce)
    	DoSomething(&doOnce)
    	// 互斥锁 Mutex
    	// 读写锁 RWMutex 读锁会阻止写但不会阻止读 RLock() RUnlock()释放
    	// 写锁就等同于Mutex
    	c := SafeCounter{v: make(map[string]int)}
    	for i := 0; i < 1000; i++ {
    		go c.Inc("somekey")
    	}
    
    	time.Sleep(time.Second)
    	fmt.Println(c.Value("somekey"))
    	// 条件变量 Cond
    	// 可以让一系列的 Goroutine 都在满足特定条件时被唤醒
    }
    
    

    原子操作

    原子操作由内置的标准库sync/atomic提供

    package main
    
    import (
    	"fmt"
    	"sync"
    	"sync/atomic"
    	"time"
    )
    
    var x int64
    var l sync.Mutex
    var wg sync.WaitGroup
    
    // 普通版加函数
    func add() {
    	// x = x + 1
    	x++ // 等价于上面的操作
    	wg.Done()
    }
    
    // 互斥锁版加函数
    func mutexAdd() {
    	l.Lock()
    	x++
    	l.Unlock()
    	wg.Done()
    }
    
    // 原子操作版加函数
    func atomicAdd() {
    	atomic.AddInt64(&x, 1)
    	wg.Done()
    }
    
    func main() {
    	start := time.Now()
    	for i := 0; i < 10000; i++ {
    		wg.Add(1)
    		// go add() // 普通版add函数 不是并发安全的
    		// go mutexAdd() // 加锁版add函数 是并发安全的,但是加锁性能开销大
    		go atomicAdd() // 原子操作版add函数 是并发安全,性能优于加锁版
    	}
    	wg.Wait()
    	end := time.Now()
    	fmt.Println("原子操作版add函数 x=", x)
    	fmt.Println("原子操作版add函数", end.Sub(start))
    }
    

    Channel通道

    Channel 是一种引用类型

    var 变量 chan 元素类型
    
    分类

    无缓冲的Channel
    发送与接受同时进行。如果没有Goroutine读取Channel(<-Channel),发送者(Channel<-x)会一直阻塞。

    有缓冲的Channel
    发送与接受并非同时进行。当队列为空,接受者阻塞;队列满,发送者阻塞。

    worker pool(goroutine池)

    指定启动的goroutine数量–worker pool模式,控制goroutine的数量,防止goroutine泄漏和暴涨。

    select多路复用

    使用select提高代码可读性

    • 可处理一个或多个channel的发送/接收操作。
      如果多个case同时满足,select会随机选择一个。
      对于没有case的select{}会一直等待,可用于阻塞main函数。
    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	ch := make(chan int, 1)
    	for i := 0; i < 10; i++ {
    		select {
    		case x := <-ch:
    			fmt.Println(x)
    		case ch <- i:
    		}
    	}
    }
    

    参考

    https://github.com/datawhalechina/go-talent/blob/master/12.并发编程.md

    https://www.cnblogs.com/nickchen121/p/11517440.html

  • 相关阅读:
    python selenium 弹框元素获取
    python小知识点
    ubuntu下配置java环境变量
    用Filezilla往ubuntu虚拟机上传文件
    jmeter,监控插件
    fiddler接口测试
    ZeroClipboard 复制到剪贴板 中文文档
    常用正则表达式
    中英文字体对照 ueditor添加字体
    字符串操作
  • 原文地址:https://www.cnblogs.com/rn-05181226-rw/p/14182092.html
Copyright © 2011-2022 走看看