[Go] cond condition variable

Posted by shar on Thu, 30 Dec 2021 15:36:45 +0100

[Go] cond condition variable

Before you look at cond, you can look at the producer consumer model

For example, there are two processes a and B, which share a fixed size buffer. Process a generates data and puts it into the buffer. Process B takes data from the buffer for calculation. In fact, this is a producer and consumer model. A is equivalent to producer and B is equivalent to consumer

Classic concurrent synchronization mode: producer consumer design mode Zhihu (zhihu.com)

So how to implement it in go language?

The first idea is to use two go co processes for cycle production and consumption respectively, but concurrency problems may occur, so it should be locked.

//package import
type Product struct{
	len  int
	cap  int
	lock sync.RWMutex
}

func main() {
	data := Product{
		len:  0,//Current inventory 0
		cap:  10,//Capacity 10
		lock: sync.RWMutex{},//Read write lock
	}
	go func() {
		//Production once in 0.1 second
		for{
			data.lock.Lock()
			if data.len==data.cap {data.lock.Unlock();continue}//If the warehouse is full, stop production and wait for consumption (continue cycle judgment)
			data.len++
			log.Printf("Production to%d piece",data.len)
			data.lock.Unlock()
			time.Sleep(100 * time.Millisecond)//Simulated production commodity time
		}
	}()
	go func() {
		//Consume once in 0.5 seconds
		for{
			data.lock.Lock()
			if data.len == 0{data.lock.Unlock();continue}//If there is no inventory, stop consumption and wait for production (continue cycle judgment)
			data.len--
			log.Printf("Consumption to%d piece",data.len)
			data.lock.Unlock()
			time.Sleep(500 * time.Millisecond)//Simulated consumption time
		}
	}()
	time.Sleep(3*time.Second)
}
  • Disadvantages: judge whether the production of goods needs to be recycled all the time, wasting resources

    Solution 1: when judging that the goods are full, rest for a fixed time

    Disadvantages: unable to produce in time during rest time

    Solution 2: rest when the goods are full, and start production when the goods are not full

    The first solution is to directly add rest time to the judgment of coordination process

    //Abbreviated context
    if data.len==data.cap {
    	data.lock.Unlock()
    	time.Sleep(100 * time.Millisecond)//Add break time
    	continue
    }
    

    The advantages are that the idling time is reduced compared with the original, and the disadvantages are obvious. It can not be produced during the rest time

    The second solution is to use the condition variable cond of go

    cond can notify and wake up other cooperation processes, reduce resource consumption and carry out production in time

    The first is to create condition variables. cond needs to be used with locks. The creation method is as follows:

    type Product struct{
    	len  int
    	cap  int
    	lock sync.RWMutex
    	cond *sync.Cond//Conditional variable
    }
    func main(){
    	data := Product{len:  0,cap:  10,lock: sync.RWMutex{},}
    	data.cond=sync.NewCond(&data.lock)//Create a condition variable to match the corresponding lock
        //......
    }
    

    After creating the condition variable, specify the following:

    ① We need to wait for production when the inventory is 0

    ② We need producers to wait for consumption when inventory is full

    Add cond. To these two judgments Wait() function, which means to enter the rest and wait to be awakened. After production or consumption, add cond Signal() (indicates waking up a blocked other process):

    func main(){
        //......
        go func() {
    		//Production once in 0.1 second
    		for{
    			data.lock.Lock()
    			if data.len==data.cap {
                    data.cond.Wait()//wait() will continue to execute downward after it is awakened by signal()
    			}
    			data.len++
    			log.Printf("Production to%d piece",data.len)
    			data.lock.Unlock()
                //End of production
                if data.len > 0{
    				data.cond.Signal()
                }
    			//Simulated production commodity time
    			time.Sleep(100 * time.Millisecond)
    		}
    	}()
    	go func() {
    		//Consume once in 0.5 seconds
    		for{
    			data.lock.Lock()
    			if data.len == 0{
    				data.cond.Wait()
    			}
    			data.len--
    			log.Printf("Consumption to%d piece",data.len)
    			data.lock.Unlock()
                //End of consumption
    			data.cond.Signal()
    			//Simulated consumption time
    			time.Sleep(500 * time.Millisecond)
    		}
    	}()
        //......
    }
    

    In this way, solution 2 is completed. The complete code is as follows:

    package main
    
    import (
    	"log"
    	"sync"
    	"time"
    )
    
    type Product struct{
    	len  int
    	cap  int
    	lock sync.RWMutex
    	cond *sync.Cond
    }
    
    func main() {
    	data := Product{
    		len:  0,//Current inventory 0
    		cap:  10,//Capacity 10
    		lock: sync.RWMutex{},
    	}
    	data.cond=sync.NewCond(&data.lock)
    	go func() {
    		// Question: judge whether the production of goods needs to be recycled all the time, wasting resources
    		// Solution 1: when judging that the goods are full, rest for a fixed time
    		// Solution 2: rest when the goods are full, and start production when the goods are not full
    		//Production once in 0.1 second
    		for{
    			data.lock.Lock()
    			if data.len==data.cap {
    				data.cond.Wait()
    			}
    			data.len++
    			log.Printf("Production to%d piece",data.len)
    			data.lock.Unlock()
    			data.cond.Signal()
    			//Simulated production commodity time
    			time.Sleep(100 * time.Millisecond)
    		}
    	}()
    	go func() {
    		//Consume once in 0.5 seconds
    		for{
    			data.lock.Lock()
    			if data.len == 0{
    				data.cond.Wait()
    			}
    			data.len--
    			log.Printf("Consumption to%d piece",data.len)
    			data.lock.Unlock()
    			data.cond.Signal()
    			//Simulated consumption time
    			time.Sleep(500 * time.Millisecond)
    		}
    	}()
    	time.Sleep(5*time.Second)
    }
    

    Let's briefly understand the wait method:

    The basic process of wait method is as follows

    • Unlock (release the lock to prevent other processes from being unable to obtain the lock)
    • Waiting for notification
    • Lock after being notified and continue to execute

As for the signal and broadcast methods, there is no upper unlock operation, but only notification.