每个资源都对应一个可称为"互斥锁"的标记,这个标记用来保证在任意时刻,只有一个协程(线程)访问该资源.其他的协程只能等待
sync.Mutex
使用互斥锁时,一定要注意:对资源操作完成后,一定要解锁,否则出现流程执行异常,死锁问题.通常借助defer.锁定后,立即使用defer语句保证互斥锁及时解锁
var mutex sync.Mutex //定义互斥锁变量
func write() {
muter.Lock()
defer muter.Unlock()
}
使用
//新创建为0.表示没有加锁,锁只有1把
var mutex sync.Mutex
func printer(str string) {
mutex.Lock()
for _, ch := range str {
fmt.Println(string(ch))
time.Sleep(300 * time.Millisecond)
}
//共享数据访问结束
mutex.Unlock()
}
func person1() {
printer("11")
}
func person2() {
printer("222")
}
func main() {
//先,因为他先获得锁
go person1()
//后
go person2()
for {
;
}
}
不是强制性的
互斥锁的使用是主动控制互斥锁,即一个愿打一个愿挨。比如即使一个routine里用了Lock(),但在另一个routine可以不理会这个锁就能访问这个struct,只需要不调用Lock()就行。例子如下
package main
import (
"fmt"
"sync"
"time"
)
type SafeCounter struct {
v map[string]int
mux sync.Mutex
}
func (c *SafeCounter) Inc(key string) {
c.mux.Lock()
time.Sleep(3 * time.Second)
c.v[key]++
c.mux.Unlock()
}
func main() {
c := SafeCounter{v: make(map[string]int)}
go c.Inc("somekey")
time.Sleep(1 * time.Second)
c.v["somekey"]++
fmt.Println(c.v["somekey"])
time.Sleep(3 * time.Second)
fmt.Println(c.v["somekey"])
}
输出
1
2
c.v["somekey"]++
已经锁定的Mutex与特定的goroutine无关联
已经锁定的Mutex并不与特定的goroutine相关联,这样可以利用一个goroutine对其加锁,再利用其他goroutine对其解锁,例子如下
package main
import (
"fmt"
"sync"
"time"
)
type MyStruct struct {
v int
mux sync.Mutex
}
func (s *MyStruct) Lock() {
s.mux.Lock()
}
func (s *MyStruct) Unlock() {
s.mux.Unlock()
}
func main() {
s := MyStruct{v: 0}
s.v = 1
fmt.Printf("%+v\n", s)
go s.Lock()
time.Sleep(1 * time.Second)
fmt.Printf("%+v\n", s)
go s.Unlock()
time.Sleep(1 * time.Second)
fmt.Printf("%+v\n", s)
}
输出
{v:1 mux:{state:0 sema:0}}
{v:1 mux:{state:1 sema:0}}
{v:1 mux:{state:0 sema:0}}
可以看出,可以一个routine里锁定,另一个routine里解锁。因为锁只和具体变量关联,和routine无关,只要这个变量是共享的,比如通过指针传递,或者全局变量都可以。
虽然互斥锁可以被直接的在多个Goroutine之间共享,但是我们还是强烈建议把对同一个互斥锁的成对的锁定和解锁操作放在同一个层次的代码块中。例如,在同一个函数或方法中对某个互斥锁的进行锁定和解锁。