Go语言并发之道学习-----死锁,活锁和饥饿

    xiaoxiao2024-12-23  53

    死锁,活锁与饥饿

    1、死锁

    死锁:会使得所有并发程序在等待,如果没有外界干预,程序不能恢复 type values struct{ mu sync.Mutex value int } func TestMutes(v1, v2 *values){ defer wg.Done() v1.mu.Lock() defer v1.mu.Unlock() time.Sleep(2*time.Second) v2.mu.Lock() defer v2.mu.Unlock() fmt.Println("v1+v2=:",v1.value+v2.value) } var wg sync.WaitGroup func main(){ var a = values{ value:1, mu:sync.Mutex{}, } var b = values{ value: 2, mu:sync.Mutex{}, } wg.Add(2) go TestMutes(&a,&b) go TestMutes(&b,&a) wg.Wait() } fatal error: all goroutines are asleep - deadlock!

    由时间问题产生死锁,代码运行时机: 检测,防止和纠正死锁的四个方式:

    相互排斥:并发进程同时拥有资源的独占权等待条件:并发进程必须同时拥有一个资源,并等待额外的资源没有抢占:并发进程拥有的资源只能被该进程释放循环等待:一个并发进程p1必须等待一系列其他并发进程p2,p2也同时在等待p1 上面的代码中goroutine的资源无法做到(没有抢占)

    2、活锁

    活锁:正在主动执行并发操作的程序,但是这些操作不能向前推进程序的状态

    //模拟人通过走廊 func main(){ cadence := sync.NewCond(&sync.Mutex{}) go func(){ for range time.Tick(1 *time.Millisecond){ cadence.Broadcast() } }() takeStep := func(){ cadence.L.Lock() cadence.Wait() cadence.L.Unlock() } //tryDir允许一个人尝试一个方向移动 tryDir := func(dirName string, dir *int32, out *bytes.Buffer)bool{ fmt.Fprintf(out,"%v",dirName) atomic.AddInt32(dir,1)//向一个方向移动 takeStep() //每个人每次移动的节奏一样 if atomic.LoadInt32(dir)==1{ fmt.Fprintf(out,".Success!") return true } takeStep() atomic.AddInt32(dir,-1)//表示不能走放弃 return false } var left, right int32 tryLeft := func(out *bytes.Buffer)bool{return tryDir(" left ",&left,out)} tryRight := func(out *bytes.Buffer) bool{return tryDir(" right ",&right,out)} walk := func(walking *sync.WaitGroup, name string){ var out bytes.Buffer defer func() {fmt.Println(out.String())}() defer walking.Done() fmt.Fprintf(&out, "%v is trying to scoot:",name) for i := 0; i < 5; i++{//对尝试次数进行限制 if tryLeft(&out) || tryRight(&out){//首先会尝试向左 return } } fmt.Fprintf(&out,"\n%v hello!",name) } var peopleIn sync.WaitGroup//模拟两个人 peopleIn.Add(2) go walk(&peopleIn,"tom") go walk(&peopleIn,"alice") peopleIn.Wait() } //结果: //alice is trying to scoot: left right left right left right left right left right alice hello! tom is trying to scoot: left right left right left right left right left right tom hello! tom和alice在退出之前会持续竞争

    3、饥饿

    饥饿:表示在任何情况下,并发进程都无法获得执行工作所需的所有资源 饥饿通常指一个或多个并发进程占有资源,使得其他进程不能占有资源进行执行

    var wg sync.WaitGroup var sharedLock sync.Mutex const runtime = 1 * time.Second greedyWorker := func(){ defer wg.Done() var count int for begin := time.Now(); time.Since(begin) <= runtime; { sharedLock.Lock() time.Sleep(3*time.Nanosecond) sharedLock.Unlock() count++ } fmt.Printf("Greedy worker was able to execute %d work loops\n",count) } politeWorker := func(){ defer wg.Done() var count int for begin := time.Now(); time.Since(begin) <= runtime; { sharedLock.Lock() time.Sleep(1*time.Nanosecond) sharedLock.Unlock() sharedLock.Lock() time.Sleep(1*time.Nanosecond) sharedLock.Unlock() sharedLock.Lock() time.Sleep(1*time.Nanosecond) sharedLock.Unlock() count++ } fmt.Printf("Polite worker was able to execute %d work loops\n",count) } wg.Add(2) go greedyWorker() go politeWorker() wg.Wait() 结果: Greedy worker was able to execute 297 work loops Polite worker was able to execute 99 work loops 从结果中可以看出greedyWorker扩大了其持有共享锁的临界区,并阻止了politeWorker的高效工作

    饥饿会导致程序性能不佳或错误,有可能使得程序出错,如果上面的例子中greedy完全阻止了polite完成工作,会使得polite永远得不到执行 在进行内存访问同步时,需要在粗粒度同步和细粒度同步之间找到平衡点,以提高程序的性能

    最新回复(0)