七叶笔记 » golang编程 » golang并发

golang并发

并发

goroutine和wg

 func main() {
    // 来实现并发任务的同步执行
    // wg 防止main的goroutine执行完了之后,自己定义的goroutine还没有执行完
    var wg sync.WaitGroup  
    for i:=0; i<5; i++{
        go func(index int) {
            defer wg.Done()  // defer会在最后执行  wg.Done 计数器减1
            fmt.Println(index)
        }(i)
        wg.Add(1) // 计数器加1  这个数字是可以随便定义的,但是在Done里自己处理
    }
    wg.Wait()  // 阻塞,直到计数器变为0
    fmt.Println("meila")
}
  

channel

  • 管道基本操作
 ch := make(chan int 10) //定义一个i能存储10个int类型的管道

ch <- 1 // 把1发送到管道中

a := <- ch // 从管道ch中拿值

close(ch) // 关闭管道
  
  • for range可以从管道中取值,通道关闭后会退出for range
  • 通道被关闭后,再从通道中取值会一直收到零值
  • 无缓冲通道必须有一个接收者,也被叫做阻塞的通道
  • 无缓冲和有缓冲的区别就是有没有在设置容量
 package main

import (
    "fmt"
)

func f3(ch chan int) {
    for v := range ch {
        fmt.Println(v)
    }
} // for range来接受通道中的值,就不用和f1自己来判断了

func f1(ch1 chan int) {
    for i := 0; i < 100; i++ {
        ch1 <- i
    }
    close(ch1)
}

func f2(ch1, ch2 chan int) {
    for {
        i, ok := <-ch1 // 多返回值模式,没有值了之后ok会是false
        if !ok {
            break
        }
        ch2 <- i * i
    }
    close(ch2)
}

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)

    go f1(ch1)
    go f2(ch1, ch2)
    /*
        执行f1和f2要加上goroutine,因为ch1和ch2都是无缓冲的通道,无缓冲的通道必须有一个接收方才能发送成功
        不加goroutine的话,就会造成一直在等待接收者,造成死锁

        无缓冲就是类似快递员送快递,必须有人签收
        有缓冲就是类似快递放到快递柜里,容量就是这个快递柜最多能放多少快递
    */
    for i := range ch2 { // for range可以用来判断通道关闭
        fmt.Println(i)
    }

    for i := 0; i < 5; i++ {
        go func() {
            fmt.Println(i)
        }()
    }

}
  
  • 单向通道可以限制管道在函数中只能发送或只能接收
 // <- chan int 只接受通道  x := <- ch 从通道中接受值
// chan <- 只发送通道  ch <- 10  把10发送到通道中
  
  • select 多路复用可处理一个或多个 channel 的发送/接收操作。如果多个 case 同时满足,select 会随机选择一个执行。对于没有 case 的 select 会一直阻塞,可用于阻塞 main 函数,防止退出。实际开发中可能存在不知道什么时候退出的情况,就可以使用select
 package main

import "fmt"

func main() {
    ch := make(chan int, 1)
    for i := 1; i <= 10; i++ {
        select {
        case x := <-ch:
            fmt.Println(x)
        case ch <- i:
        }
    }
}
/* 输出
1
3
5
7
9
*/  

并发和锁

  • 为了防止多个goroutine操作同一个资源,需要在操作的时候加上锁
  • 锁有互斥锁和读写互斥锁
 package main

import (
    "fmt"
    "sync"
    "time"
)

var (
    x       int64
    wg      sync.WaitGroup
    mutex   sync.Mutex   // 互斥锁,同一时间只有一个人能用
    rwMutex sync.RWMutex // 读写互斥锁,用于读多写少的场景
    /*
        读写锁分为两种:读锁和写锁。
        当一个 goroutine 获取到读锁之后,其他的 goroutine 如果是获取读锁会继续获得锁,如果是获取写锁就会等待;
        当一个 goroutine 获取写锁之后,其他的 goroutine 无论是获取读锁还是写锁都会等待
    */)

func writeWithLock() {
    mutex.Lock() // 加锁
    x = x + 1
    time.Sleep(10 * time.Millisecond)
    mutex.Unlock() // 解锁
    wg.Done()
}

func readWithLock() {
    mutex.Lock()
    time.Sleep(time.Millisecond)
    mutex.Unlock()
    wg.Done()
}

func writeWithRWLock() {
    rwMutex.Lock() // 加写锁
    x = x + 1
    time.Sleep(10 * time.Millisecond)
    rwMutex.Unlock() //解开写锁
    wg.Done()
}

func readWithRWLock() {
    rwMutex.RLock() // 加读锁
    time.Sleep(time.Millisecond)
    rwMutex.RUnlock() // 解开读锁
    wg.Done()
}

func do(wf, rf func(), wc, rc int) {
    start := time.Now()

    // wc个并发写操作
    for i := 0; i < wc; i++ {
        wg.Add(1)
        go wf()
    }

    // rc个并发读操作
    for i := 0; i < rc; i++ {
        wg.Add(1)
        go rf()
    }

    wg.Wait()
    cost := time.Since(start)
    fmt.Printf("x:%v cost:%v\n", x, cost)
}

func main() {
    do(writeWithLock, readWithLock, 10, 1000)
    do(writeWithRWLock, readWithRWLock, 10, 1000) // x:10 cost:117.207592ms
}
  

sync方法

  • sync.WaitGroup 用来实现并发任务的同步Add(x) 计数器加xDone() 计数器减1Wait() 阻塞,直到计数器变为0
  • sync.Once 确保某些操作只会执行一次,比如加载配置文件之类的
 package main

import (
    "image"
    "sync"
)

var icons map[string]image.Image

var loadIconsOnce sync.Once

func loadIcons() {
    icons = map[string]image.Image{
        /*
            不用Sync.Once的话,在并发操作时,可能刚初始化完map就会被调用,倒是值一直时nil
        */        "left":  loadIcon("left.png"),
        "up":    loadIcon("up.png"),
        "right": loadIcon("right.png"),
        "down":  loadIcon("down.png"),
    }
}

// Icon 是并发安全的
func Icon(name string) image.Image {
    loadIconsOnce.Do(loadIcons)
    /*
        sync.Once只有一个Do方法
        确保了loadIcon只会被执行一次
    */    return icons[name]
}
  
  • sysc.map 并发的时候使用的map
 package main

import (
    "fmt"
    "strconv"
    "sync"
)

// 并发安全的map
var m = sync.Map{}

func main() {
    wg := sync.WaitGroup{}
    // 对m执行20个并发的读写操作
    for i := 0; i < 20; i++ {
        wg.Add(1)
        go func(n int) {
            key := strconv.Itoa(n)
            m.Store(key, n)         // 存储key-value
            value, _ := m.Load(key) // 根据key取值
            fmt.Printf("k=:%v,v:=%v\n", key, value)
            wg.Done()
        }(i)
    }
    wg.Wait()
}  

相关文章