Go语言select线程和安全

1.多channel场景

1.多个channel同时需要读取或写入,怎么办?

  • 多管道读写时是一个串行过程,前面的管道会阻塞后面代码的运行
package main

import (
    "fmt"
    "time"
)

func server1(ch chan string) {
    time.Sleep(6 * time.Second)
    ch <- "from server1"
}

func server2(ch chan string) {
    time.Sleep(3 * time.Second)
    ch <- "from server2"
}

func main() {
    output1 := make(chan string)
    output2 := make(chan string)
    go server1(output1)
    go server2(output2)
    s1 := <-output1
    fmt.Println(s1)
    s2 := <-output2  // server2只需要3秒就已经准保好了,但是s1需要6秒。所以s2只能等待(串行)
    fmt.Println(s2)
}

/* 6秒后输出
from server1
from server2
*/

2.select引出

  • 有点像switch case,哪个管道准备好了哪个管道先读写
  • 同时监听一个或者多个channel,直到其中一个channel ready
  • 如果其中多个channel同时ready,则会随机选择一个进行操做
  • default分支,当case分支的channel都没有ready的话,则会进入default分支
    • 用来判断channel是否满了
    • 用来判断channel是否是空的
select {
case s1 := <-output1:
    fmt.Println(s1)
case s2 := <-output2:
    fmt.Println(s2)
default:
    fmt.Println("没有管道可读取/写入")
}

2.线程安全介绍

1.现实例子

  • 1.多个goroutine同时操做一个资源,这个资源又叫临界区
  • 2.现实生活中的十字路口,通过红绿灯实现线程安全
  • 3.火车上的厕所,通过互斥锁来实现线程安全

2.实际例子:x = x + 1

  • 1.先从内存中取出x的值
  • 2.CPU进行计算,x + 1
  • 3.然后把x + 1的结果存储在内存中
package main

import "fmt"

var x int

func Add()  {
    for i := 0; i < 5000; i++ {
	x = x + 1
    }
}

func main() {
    Add()
    Add()
    fmt.Println("x ", x)  // x  10000
}
  • 模拟线程安全
package main

import (
    "fmt"
    "sync"
)

var x int
var wg sync.WaitGroup

func Add()  {
    for i := 0; i < 5000; i++ {
	x = x + 1
    }
    wg.Done()
}

func main() {
    wg.Add(2)
    go Add()
    go Add()
    wg.Wait()  // 等待子线程结束
    fmt.Println("x ", x)  // x 6939
}

3.互斥锁介绍

  • 1.同时有且只有一个线程进入临界区,其他线程则在等待锁
  • 2.互斥锁释放之后,等待锁的线程才可以获取锁进入临界区
  • 3.多个线程同时等待一个锁,唤醒的策略是随机的
  • 4.互斥锁是引用类型
package main

import (
    "fmt"
    "sync"
)

var x int
var wg sync.WaitGroup
var mutex sync.Mutex  // 如果使用局部定义,一定要传递地址,他是引用类型

func Add()  {
    for i := 0; i < 5000; i++ {
        mutex.Lock()
	x = x + 1
        mutex.Unlock()
    }
    wg.Done()
}

func main() {
    wg.Add(2)
    go Add()
    go Add()
    wg.Wait()  // 等待子线程结束
    fmt.Println("x ", x)  // x 6939
}

4.读写锁使用场景

  • 1.读多写少的场景
  • 2.分为两种角色,读锁和写锁
  • 3.当一个goroutine获取写锁之后,其他goroutine获取读锁写锁都会等待
  • 4.当一个goroutine获取读锁之后,其他的goroutine获取写锁都会等待,但其他goroutine获取读锁时,都会继续获得锁
  • 5.读的时候是并发,写的时候是串行
package main

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

var wg sync.WaitGroup
var rwlock sync.RWMutex
var y int

func write()  {
    rwlock.Lock()
    fmt.Println("write lock")
    y = y + 1
    time.Sleep(10 * time.Second)  // 获取写锁后读锁也无法进入,会等待10秒
    rwlock.Unlock()
    fmt.Println("write unlock")
    wg.Done()
}

func read(i int)  {
    fmt.Println("wait for rlock")
    rwlock.RLock()
    fmt.Printf("goroutine: %d y=%d\n", i, y)
    time.Sleep(time.Second)  // 测试读是并发的,如果是串行则需要10秒
    rwlock.RUnlock()
    wg.Done()
}

func main()  {
    wg.Add(1)
    go write()

    time.Sleep(time.Second)

    for i := 0; i < 10 ; i++ {
	wg.Add(1)
	go read(i)
    }

    wg.Wait()
}

5.原子操做

  • 加锁代价比较耗时,需要切换上下文

  • 针对基本数据类型,可以使用原子操作保证线程安全

  • 原子操作在用户态就可以完成,因此性能比互斥锁要高

  • 原子操做只适合整型

  • 1.原子操做耗时统计(线程数越大,原子操做优势越明显)

package main

import (
    "fmt"
    "sync"
    "sync/atomic"
    "time"
)

var x int64
var wg sync.WaitGroup

func Add()  {
    for i := 0; i < 5000; i++ {
	atomic.AddInt64(&x, 1)
    }
    wg.Done()
}

func main() {
    start := time.Now().UnixNano()
    for i := 0; i < 1000; i++ {
	wg.Add(1)
	go Add()
    }

    wg.Wait()  // 等待子线程结束
    end := time.Now().UnixNano()
    cost := (end - start) / 1000 / 1000
    fmt.Println("x: ", x, "cost: ", cost, "ms")  // x:  5000000 cost:  102 ms
}
  • 2.互斥锁耗时统计
package main

import (
    "fmt"
    "sync"
    "sync/atomic"
    "time"
)

var x int64
var wg sync.WaitGroup

func Add()  {
    for i := 0; i < 5000; i++ {
	atomic.AddInt64(&x, 1)
    }
    wg.Done()
}

func main() {
    start := time.Now().UnixNano()
    // 1000个线程
    for i := 0; i < 1000; i++ {
	wg.Add(1)
	go Add()
    }

    wg.Wait()  // 等待子线程结束
    end := time.Now().UnixNano()
    cost := (end - start) / 1000 / 1000
    fmt.Println("x: ", x, "cost: ", cost, "ms")  // x:  5000000 cost:  263 ms
}
posted @ 2022-09-28 11:52  fatpuffer  阅读(140)  评论(0)    收藏  举报