GO并发(一)Mutex的实现机制与基本使用 | 青训营

125 阅读3分钟

Go标准库中,用Mutex实现互斥锁

Mutex是使用最广泛的同步原语(Synchronization primitives),也有人叫并发原语。并发原语都包括互斥锁Mutex、读写锁RWMutex、并发编排WaitGroup、条件变量Cond、Channel

同步原语的适用场景

  • 资源共享。并发地读写共享资源,会出现数据竞争(data race)的问题,所以需要Mutex、RWMutex来保护
  • 任务编排。需要gotoutine按照一定的规律执行,而goroutine之间有相互等待或者依赖的顺序关系,通常用WaitGroup或者Channel来实现
  • 消息传递。信息交流以及不同的goroutine之间的线程安全和数据交流,常用Channel来实现

互斥锁的实现机制

临界区:

在并发编程中,如果程序中的一部分会被并发访问或修改,那么,为了避免并发访问导致的意想不到的结果,这部分程序需要被保护起来,这部分被保护起来的程序,就叫做临界区。(其实就是一个被共享的资源)

Mutex的基本使用

Locker接口

Locker接口定义了锁同步原语的方法集

type Locker interface{
    Lock()
    Unlock()
}

但这个接口在实际中用的不多,一般用具体的同步原语

Lock & Unlock

互斥锁Mutex提供了两个方法Lock和Unlock: 进入临界区前调用Lock方法,推出临界区的时候调用Unlock方法。Lock和Unlock一定要成对出现

func(m *Mutex)Lock()
func(m *Mutex)Unlock()

这里有一点需要注意:Mutex 的零值是还没有 goroutine 等待的未加锁的状态,所以你不需要额外的初始化,直接声明变量(如 var mu sync.Mutex)即可。

很多情况下Mutex会嵌入到其他struct中使用,比如:

type Counter struct{
    mu sync.Mutex
    Count uint64
}

未加锁的例子

创建10个goroutine,同时不断地对一个变量(count)进行加 1 操作,每个 goroutine 负责执行 10 万次的加 1 操作,我们期望的最后计数的结果是 10 * 100000 = 1000000 (一百万)。

import(
    "fmt"
    "sync"
)
func main(){
    var count = 0
	//辅助变量sync.WaitGroup
    var wg sync.WaitGroup
	
    //加goroutine
    wg.Add()

    for i:=0;i<10;i++{
        go func(){
            defer wg.Done

            //count++
            for j :=0;j<100000;j++{
                count++
            }
        }()
    }
	//等待10个goroutine完成
    wg.Wait()
    fmt.Println(count)
}

结果不会是1000000,因为count++不是原子操作,会出现data race的问题。

加锁

package main

import(
    "fmt"
    "sync"
)

func main(){
    //互斥锁
    var mu sync.Mutex
	count:=0

    //辅助变量, 用于控制所有的goroutine都完成
    var wg  mu.WaitGroup
    wg.Add(10)
    for i := 0 ;i <10;i++{
        go func(){
            for j := 0 ;j < 100000;j++{
                mu.Lock()
                count++
                mu.Unlock()
            }
        }()
    }
    wg.Wait()
    fmt.Println(count)
    
}

锁写进 结构体/方法

package main
import(
    "fmt"
    "sync"
)
type Counter struct{
    sync.Mutex
    Count int 
}
func main(){
    var wg sync.WaitGroup
    var counter Counter
    wg.Add(10)
    for i:=0;i<10;i++{
        go func(){
            defer wg.Done()
            for j := 0 ; j<100000;j++{
                counter.Lock()
                counter.Count++
                counter.Unlock()
            }
        }()
    }
    wg.Wait()
    fmt.Println(counter.count)
    
}
package main
import(
    "fmt"
    "sync"
)
type Counter struct{
    sync.Mutex
    Count int
}
func (counter *Counter) Incr(){
    counter.Lock()
    counter.Count++
    counter.Unlock()
} 
func (counter *Counter) GetValue() int{
    counter.Locker 
    defer counter.Unlock
    return counter.Count
}
func main(){
	var counter Counter
    var wg snyc.WaitGroup
    wg.Add(10)

    for i:=0;1<10;i++{
        go func(){
        	defer wg.Done()
            for j :=0;j<100000;j++{
                counter.Incr()
            }
    	}()
    }
    wg.Wait()
    fmt.Println(counter.GetValue)
}