互斥鎖和讀寫鎖的區別
當多個協程進行讀寫操作時,採用互斥鎖的話,將會一個一個讀並一個一個寫,
當多個協程進行讀寫操作時,採用讀寫鎖的話,讀操作將併發進行,而寫操作將一個一個進行,相比於互斥鎖,讀寫瑣這樣能有效的節約讀的時間
示例問題
x是一個0-99的100位數的數組,y是由全是0組成的100位數組
開啓兩個讀取x的協程如下:
- 協程1:將x中的每個元素乘以2,並讀到y中,協程2:將x中的每個元素乘以3,並讀到y中
再開啓兩個重寫x協程如下:
- 協程3:一個將x中的每個元素乘以2,並寫入x中,協程4:將x中的每個元素乘以3,並寫到x中
此時,協程1和協程2並未對數組x進行任何寫操作,僅作了讀操作,而協程3和協程4都對x進行了寫操作
互斥鎖示例代碼如下:
func test1() {
var x [100]int
var y [100]int
for i := 0; i < 100; i++ {
x[i] = i + 1
y[i] = 0
}
fmt.Println("before, x= ", x) //此時 x爲[1-100]
fmt.Println("before, y= ", y) //此時 y爲[0-0]
var wg sync.WaitGroup //用來等待,防止開啓的go協程在主函數中直接飛掉
var lock sync.Mutex //互斥鎖
wg.Add(4) //等待的任務個數
reader := func() {
//協程1 將x中的每個元素x2,並讀到y中
go func(a *[100]int, b *[100]int) {
lock.Lock()
defer lock.Unlock()
for index := 0; index < len(a); index++ {
b[index] = a[index] * 2
fmt.Println("協程1正在讀取中..... index=", index, "value=", b[index])
}
fmt.Println("協程1結束, r1 b= ", b)
wg.Done() //結束1一個等待任務
}(&x, &y)
//協程2 將x中的每個元素x3,並讀到y中
go func(a *[100]int, b *[100]int) {
lock.Lock()
defer lock.Unlock()
for index := 0; index < len(a); index++ {
b[index] = a[index] * 3
fmt.Println("協程2正在讀取中..... index=", index, "value= ", b[index])
}
fmt.Println("協程2結束, r2 b= ", b)
wg.Done()
}(&x, &y)
}
write := func() {
//協程3 一個將x中的每個元素x2,並寫入x中
go func(a *[100]int) {
lock.Lock()
defer lock.Unlock()
for index := 0; index < len(a); index++ {
a[index] *= 2
fmt.Println("協程3正在寫..... index=", index, "value= ", a[index])
}
fmt.Println("協程3結束, value= ", a)
wg.Done()
}(&x)
//協程4 將x中的每個元素x3,並寫到x中
go func(a *[100]int) {
lock.Lock()
defer lock.Unlock()
for index := 0; index < len(a); index++ {
a[index] *= 3
fmt.Println("協程4正在寫..... index=", index, "value= ", a[index])
}
fmt.Println("協程4結束, value= ", a)
wg.Done()
}(&x)
}
reader() //先開始兩個讀的協程
time.Sleep(1*time.Second) //等待一秒
write() //再開啓兩個寫的協程
wg.Wait()
fmt.Println("result x:", x) //x的結果固定爲6-600,但過程由協程3和協程4競爭先後決定
fmt.Println("result y:", y) //y的結果由協程1和2競爭決定,協程1在後,則爲2-200,協程2在後則爲3-300
}
在上面的示例中,我們可以看到,打印出來的結果,當讀操作時,x依次被傳入協程1和協程2中,進行相應的讀取,在其中一個讀操作結束後,再進行下一個讀操作;
讀寫鎖示例代碼如下:
func test2() {
var x [100]int
var y [100]int
for i := 0; i < 100; i++ {
x[i] = i + 1
y[i] = 0
}
fmt.Println("before test1, x= ", x)
fmt.Println("before test1, y= ", y)
var wg sync.WaitGroup //用來等待,防止開啓的go協程在主函數中直接飛掉
var lock sync.RWMutex //讀寫瑣
wg.Add(4) //等待的任務個數
reader := func() {
//協程1 將x中的每個元素x2,並讀到y中
go func(a *[100]int, b *[100]int) {
lock.RLock() //讀瑣開啓
defer lock.RLock() //讀瑣關閉
for index := 0; index < len(a); index++ {
b[index] = a[index] * 2
fmt.Println("協程1正在讀取中..... index=", index, "value=", b[index])
}
fmt.Println("協程1結束, r1 b= ", b)
wg.Done() //結束1一個等待任務
}(&x, &y)
//協程2 將x中的每個元素x3,並讀到y中
go func(a *[100]int, b *[100]int) {
lock.RLock()
defer lock.RLock()
for index := 0; index < len(a); index++ {
b[index] = a[index] * 3
fmt.Println("協程2正在讀取中..... index=", index, "value= ", b[index])
}
fmt.Println("協程2結束, r2 b= ", b)
wg.Done()
}(&x, &y)
}
write := func() {
//協程3 一個將x中的每個元素x2,並寫入x中
go func(a *[100]int) {
lock.Lock()
defer lock.Unlock()
for index := 0; index < len(a); index++ {
a[index] *= 2
fmt.Println("協程3正在寫..... index=", index, "value= ", a[index])
}
fmt.Println("協程3結束, value= ", a)
wg.Done()
}(&x)
//協程4 將x中的每個元素x3,並寫到x中
go func(a *[100]int) {
lock.Lock()
defer lock.Unlock()
for index := 0; index < len(a); index++ {
a[index] *= 3
fmt.Println("協程4正在寫..... index=", index, "value= ", a[index])
}
fmt.Println("協程4結束, value= ", a)
wg.Done()
}(&x)
}
reader() //先開始兩個讀的協程
time.Sleep(1*time.Second) //等待一秒
write() //再開啓兩個寫的協程
wg.Wait()
fmt.Println("result x:", x) //x的結果固定爲6-600,但過程由協程3和協程4競爭先後決定
fmt.Println("result y:", y) //y的結果由協程1和2競爭決定,協程1在後,則爲2-200,協程2在後則爲3-300
}
在上面的示例中,我們可以看到,打印出來的結果,當讀操作時,x依次被同時傳入協程1和協程2中,進行相應的讀取,但在寫操作時,x又依次進入寫的操作裏,在其中一個寫操作結束後,再進行下一個寫操作;
由此可見,讀寫瑣在進行讀寫操作時,最大的優點是,節約讀的操作時間