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

死锁,活锁与饥饿

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永远得不到执行
在进行内存访问同步时,需要在粗粒度同步和细粒度同步之间找到平衡点,以提高程序的性能

猜你喜欢

转载自blog.csdn.net/alvin_666/article/details/90575032