贺胖娇的编程之旅......

学习go_20220807_01

2022.08.07

Lock

go语言中有两种锁:互斥锁和读写锁

互斥锁Mutex

常用于对并发资源的限制
如果不加锁,在协程中并发执行,最后结果可能非预期:

func TestLock(t *testing.T) {
	counter := 0
	for i := 0; i < 500; i++ {
		go func() {
			counter++
		}()
	}
	t.Logf("counter %d\n", counter) // 结果几乎都不是500
}

使用Mutex加锁后执行:

func TestMutexLock(t *testing.T) {
	counter := 0
	var mut sync.Mutex
	for i := 0; i < 500; i++ {
		go func() {
			defer func() {
				mut.Unlock()
			}()
			mut.Lock()
			counter++
		}()
	}
	time.Sleep(time.Second * 1) // 这里不加sleep结果不一定正确
	t.Logf("counter %d\n", counter) // 当sleep 1s且循环次数不算特别大时,结果是正确的
}

会注意到,使用Mutex加锁后,在最后输出结果前sleep了1s,如果不执行sleep,结果大概率是不正确的,为什么呢?
因为主协程可能执行得比较快,导致还没计算出真正的结果就已经输出,因此sleep
但这里可以提出一个新的问题,如何界定这个sleep的时间?有没有可能sleep设置得不够?因此引出新的方式WaitGroup

WaitGroup

func TestMutexWaitGroup(t *testing.T) {
	counter := 0
	var mut sync.Mutex
	var wg sync.WaitGroup
	for i := 0; i < 500; i++ {
		wg.Add(1)
		go func() {
			defer func() {
				mut.Unlock()
			}()
			mut.Lock()
			counter++
			wg.Done()
		}()
	}
	wg.Wait()
	t.Logf("counter %d\n", counter) // 结果几乎都不是500
}

读写锁RWMutex

1 RWLock读是非互斥锁,写是互斥锁
2 读写锁的读锁可以重入,在已经有读锁的情况下,可以任意加锁
3 在读锁没有全部解锁的情况下,写操作会阻塞直到所有读锁解锁
4 写锁定的情况下,其他协程的读写都会被阻塞,直到写锁解锁
5 读写锁本身是互斥的,读需要写锁都解锁,写需要读锁都解锁

channel

无buffer的channel

func server() string {
	time.Sleep(time.Second * 2)
	fmt.Println("run server...")
	return "server"
}

func serverChan() chan string {
	chanTest := make(chan string)
	for i := 0; i <= 100; i++ {
		go func() {
			ret := server()
			chanTest <- ret
		}()
	}
	return chanTest
}
func TestChannel(t *testing.T) {
	retChan := serverChan()
	t.Log(<-retChan)
}

有buffer的channel

以上代码,make chan的时候提供buffer长度:

chanTest := make(chan string)

select

select中的case表示随机取到其中一个,当包含time.After时,会看是否到达设置的时间,到达则表示超时,执行对应代码

多渠道选择

多个渠道,随机执行:

func TestSelectChan(t *testing.T) {
	retCh1 := serverChan()
	retCh2 := anotherServerChan()
	time.Sleep(time.Second * 2)
	select {
	case ret := <-retCh1:
		t.Logf("ret: %v\n", ret)
	case ret := <-retCh2:
		t.Logf("ret: %v\n", ret)
	case <-time.After(time.Second * 1):
		t.Log("break......")
	}
}

超时控制

上文中的case <-time.After(time.Second * 1)即为超时控制

channel的关闭与广播

向关闭的channel发送数据,会导致panic:

func closeChan() chan string {
	chanTest := make(chan string)
	for i := 0; i <= 10; i++ {
		go func() {
			chanTest <- "aaa"
		}()
	}
	close(chanTest)
	return chanTest
}

func TestSendCloseToChannelPanic(t *testing.T) {
	closeChan()
}

事实上,以上代码看起来好像是最后才关闭的chan,但实际上协程可能没执行完,当for循环的次数足够多,几乎都会出现以下报错:

panic: send on closed channel

对于此案例来说,要想解决问题,可以尝试之前用过的加锁方式:

判断channel是否正常

发表评论