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
}

浙公网安备 33010602011771号