Lock vs Semaphore vs Condition Variable vs Monitor

目录

1. Critical Section(CS)

2. Lock 

3. Semaphore 

4. Condition variable

5. Monitor

6. Summary


In mutil-threads (concurrent) programing, two or more threads have access to a shared memory, for avoiding confusing results, these threads should access the shared memory in proper order, this is also called sychronization. But what is more, in order to make sychronization more efficient, these threads may communicate with each other. This article is mainly about sychronization and communication.

1. Critical Section(CS)

Critical section is a piece of codes of a program that should not be accessed by multiple threads at the same time. It should be accessed in mutual exclusion. At most one thread can enter in CS at a time. 

2. Lock 

Lock provides a means to achieve mutual exclusion. It is guaranteed that only one thread can enter in critical section at a time. Let us have a look at the following pesudo codes.

Lock num_mutex; // num_mutex is a lock

######################################


Thread1:

// acquire lock
mutex_lock(&num_mutex);

// ******** critical section *******
num++;
// ******** critical section *******

// release lock
mutex_unlock(&num_mutex); 


######################################


Thread2:

// acquire lock
mutex_lock(&num_mutex);

// ******** critical section *******
num++;
// ******** critical section *******

// release lock
mutex_unlock(&num_mutex);

As shown in above codes, num++ is not an atomic operation, so num++  should be excuted by only one thread at a time. When Thread1 and Thread2 are acquiring the same lock simultaneously, only one thread will get the lock finally, and then enter in CS,  the other thread will be waiting until the lock is released.

For classic producer-consumer problem, we can use Lock to solve it.

Lock count_mutex; // count_mutex is a lock


#############################################



Producer Thread

// acquire lock
mutex_lock(&count_mutex);

// if count is equal to MAX_SIZE, do not produce and then sleep to wait
while(count == MAX_SIZE){
    
    // release lock to give lock to consumer
    mutex_unlock(&count_mutex);

    Thread.sleep(3000); // sleep for 3s
    
    // awake to acquire lock again
    mutex_lock(&count_mutex);
}

// ***** critical section ****//

// produce 
produce();

// after producing, increment count by 1
count++;

// ***** critical section ****//

// release lock after producing
mutex_unlock(&count_mutex);



##############################################


Consumer Thread

// acquire lock
mutex_lock(&count_mutex);

// if count is equal to 0, do not consume and then sleep to wait
while(count == 0){
    
    // release lock to give lock to producer
    mutex_unlock(&count_mutex);

    Thread.sleep(3000); // sleep for 3s
    
    // awake to acquire lock again
    mutex_lock(&count_mutex);
}

// ***** critical section ****//

// consume
consume();

// after consuming, decrement count by 1
count--;

// ***** critical section ****//

// release lock after consuming
mutex_unlock(&count_mutex);


There are two threads: Producer and Consumer, Producer is producing until count is equal to MAX_SIZE, similarly Consumer is consuming until count is equal to 0,  (produce(); count++) and   (consume(); count--)  are critical section, either Producer or Consumer enters in the critical section, they must acquire the lock(count_mutex) before, and only one will acquire the lock at a time. But once one thread gets the lock and immediately checks whether the condition is satisfied, if not satisfied it will wait until the condition is satisfied. In this case, Producer and Consumer check whether count is satisfied,  if not they will enter a while loop: release lock and sleep for three seconds and then wake up to try to acquire the lock again. 

Solving with producer-consumer problem using lock seems not bad, but as we see in the case above, producer and consumer do not communicate with each other, they have to wait for some condition to become true by repeatedly checking, it will cause busy waiting. Busy waiting wastes cpus cycles and slows down runing threads, so we should try our best to avoid busy waiting.

3. Semaphore 

A semaphore is usually termed as a set of permit that controls access to a common resource by multiple threads. A semaphore is initialized with a integer value N( N>0 ), N means the maximum number of threads that can share a common resource concurrently.  Semaphore has two operations: wait(or P) and signal(or V), wait decrements the value of semaphore by 1 and causes the calling thread blocked(added to a waiting queue) if the new value is negative, signal increments the value of semaphore by 1 and wakes up one thread in waiting queue, the awaked thread is ready for being scheduled again.

An example for producer-consumer problem with Semaphore as belows:

// emptyCount, mutex and fullCount are a Semaphore
Semaphore emptyCount = N, mutex = 1, fullCount = 0; 


####################################################


Producer Thread

// decrement emptyCount by 1 to get permit to produce, 
// producer will be put in waiting queue if new value is negative 
sem_wait(&emptyCount);

sem_wait(&mutex);

// ******** critical section *********

produce();

// ******** critical section *********

sem_signal(&mutex);

// successfully produce, increment fullCount by 1 
// and causes the waiting consumer resume consuming 
sem_signal(&fullCount);



################################################################


Consumer Thread

// decrement fullCount by 1 to get permit to consume
// consumer will be put in waiting queue if new value is negative 
sem_wait(&fullCount);

sem_wait(&mutex);

// ******** critical section *********

consume();

// ******** critical section *********

sem_signal(&mutex);

// successfully consume, increment emptyCount by 1 
// and causes the waiting producer resume producing 
sem_signal(&emptyCount);


In the above codes, Producer and Consumer communicate with each other by operating Semaphore (emptyCount and fullCountto avoid busy waiting. Note that mutex is also a Semaphore, mutex is initialized with 1, in this case it  is used as a lock to protect the critical section.  Some articles regard a semaphore with initial value 1 as a lock, but there are some significant differences between lock and semaphore.

SemaphoreLock
resources occupiedany thread can release the resource(any thread can call signal() )

the resource can only be released by the thread that owns the lock

acquire/release

a thread don't need to release resource after successfully acquiring the resourceA thread must release the resource aflter acquiring the resource, otherwise it will cause dead-lock.

Because of these differences between Semaphore and Lock, Lock pays more attention to resources and the owership of resources while Semaphore focuses on communication between threads.

4. Condition variable

Condition variable takes advantages of Semaphore, it can be seen as a container of waiting threads like a semaphore, differently it has to be used with a mutex(lock). Condition variable has three operations: wait(), signal() and broadcast()wait() causes the current thread to release the lock and wait until it is awaked, note that the current thread must own the lock before. signal() wakes up one waiting thread and the awaked thread is ready for acquiring the lock again and resuming executing( if more than one threads are acquiring the lock, the awaked thread and other threads compete to acquire the lock fairly). broadcast() is similar to signal() but it will wake up all waiting threads. These operations are illustrated in the picture below :

Then let us use Condition variable to modify the above producer-consumer example with Lock.

// condi_produce and condi_consume are a Condition variable
Condition condi_produce, condi_consume;


#########################################################


Producer Thread

// acquire lock
mutex_lock(&count_mutex);

// if count is equal to MAX_SIZE, do not produce and then wait
while(count == MAX_SIZE){
    
    // release lock(count_mutex) and wait on condi_produce
    condition_wait(&condi_produce, &count_mutex);
}

// ***** critical section ****//

// produce 
produce();

// after producing, increment count by 1
count++;

// ***** critical section ****//

mutex_unlock(&count_mutex);

// notify Consumer to resume consuming
condition_signal(&condi_consume);



##############################################


Consumer Thread

// acquire lock
mutex_lock(&count_mutex);

// if count is equal to 0, do not consume and then wait
while(count == 0){
    
    // release lock(count_mutex) and wait on condi_consume 
    condition_wait(&condi_produce, &condi_consume);
}

// ***** critical section ****//

// consume
consume();

// after consuming, decrement count by 1
count--;

// ***** critical section ****//

mutex_unlock(&count_mutex);

// notify Producer to resume producing
condition_signal(&condi_produce);


With Conditional Variable, it provides a means in which Producer and Consumer communicate with each other to notify each other to resume executing instead of busy waiting. So Condition variable is definitely a good partner of Lock.

5. Monitor

Java provides a efficient built-in synchronization method called Monitor.  Monitor is similar to Lock(Mutex) + Condition variable. Every object in java has its own monitor,  but we can not get a object of Monitor directly, instead Java provides Synchronized, Object.wait(),  Object.notify() and Object.notifyAll() to achieve synchronization based on Monitor. Let us find out how to solve producer-consumer problem with Monitor.

//sync_object is a object and asscociated with a monitor
Object sync_object;


################################################# 


Producer Thread

// acquire lock on sync_object, 
synchronized(sync_object){

    // check whether count is equal to MAX_SIZE
    while(count == MAX_SIZE){
        
        // release lock, and wait 
        sync_object.wait();
    }

    // produce 
    produce();

    // after producing, increment count by 1
    count++;

    // notify Consumer to resume consuming
    sync_object.notify();
}



##############################################


Consumer Thread

// acquire lock on sync_object, 
synchronized(sync_object){

    // check whether count is equal to 0
    while(count = 0){
        
        // release lock, and wait 
        sync_object.wait();
    }

    // consume
    consume();

    // after consuming, decrement count by 1
    count--;

    // notify Producer to resume producing
    sync_object.notify();
}


As shown in the example, Synchronized acquires the monitor lock of sync_object, wait() releases the monitor lock and puts the current thread into waiting queue, notify() wakes up one waiting thread.

6. Summary

Concurrent programing not only needs synchronization but also needs to achieve synchronization more efficiently.

👉👉👉 自己搭建的租房网站:全网租房助手,m.kuairent.com,每天新增 500+房源

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值