虽然互斥锁实现了线程之间的互斥,但是互斥锁也有不足之处,它只能表示两种状态:上锁和非上锁。但是假如有线程A拿着锁进入临界区,并在临界区休眠了。而此时正在等待该锁的线程就会不断轮询,查看锁是否已经被释放。当线程A释放锁后,所有在该锁上阻塞的线程都会变成可运行状态,第一个变成可运行状态的线程会先获得锁,其他线程会继续等待直到变为可用。
引入条件变量一个就是为了避免为了查看条件是否成立而不断轮询的情况,这样也提高了效率;另一个就是为了防止竞争,条件变量用来阻塞一个线程,当条件不满足时,线程往往会解开互斥锁并等待条件发生变化,一旦有某个线程改变了条件变量,它会通知该条件变量下的一个或多个正在被该条件变量阻塞的线程,这些线程会重新上锁并检测条件是否成立。
总结:互斥锁实现的是线程之间的互斥,条件变量实现的是线程之间的同步。
对条件变量的理解:
1、条件变量与互斥锁一样,都是一种数据;
2、条件变量的作用是描述当前资源的状态,即当前资源是否就绪。
3、条件变量是在多线程程序中用来实现“等待->唤醒”逻辑的常用方法。
条件变量的接口函数:
1.初始化:
//利用宏进行初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
//初始化函数
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
如果ConditionVariable是静态分配的,可以用宏定义PTHEAD_COND_INITIALIZER初始化,相当于用pthread_cond_init函数初始化并且attr参数为NULL。
2.销毁:
int pthread_cond_destroy(pthread_cond_t *cond);
与mutex类似,该函数用来销毁一个条件变量。
3.等待:
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
该函数用来在一个ConditionVariable上阻塞等待,做以下三步操作:①释放Mutex;②阻塞等待;③当被唤醒时,重新获得Mutex并返回。
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
pthread_cond_timedwait()函数有一个额外的参数可以设定等待超时,如果到达了abstime所指定的时刻仍然没有别的线程来唤醒当前线程,就返回ETIMEDOUT。
4.唤醒等待线程:
int pthread_cond_signal (pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
pthread_cond_signal():用于唤醒在该条件变量下等待的一个线程,至于哪个被唤醒,取决于线程的优先级和调度策略。
pthread_cond_broadcast():用于唤醒在某个ConditionVariable 上等待的所有线程。使用这种方法时,瞬间唤醒的线程数过多,资源请求过大,容易导致系统不稳定。
综上:可以理解信号量是一种计数器,P操作就是将计数器的值减1,执行一次P操作意味着请求一次资源,即当前可以获得资源;V操作就是将计数器的值加1,即执行一次V操作意味着释放一个单位资源。
int sem_init(sem_t *sem, int pshared, unsigned int value);
value参数表示可用资源的数量,pshared参数为0表示信号量用于同一进程的线程间同步。
2.销毁:
int sem_destroy(sem_t *sem);
该函数用于释放与Semaphore相关的资源。
3.等待(P操作):
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
sem_wait()被用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值将减一,表明公共资源经使用后减少。
sem_trywait()是sem_wait()的非阻塞版本,它直接将信号量sem的值减一。
4.释放(V操作):
int sem_post(sem_t *sem);
用来增加信号量的值。当有线程阻塞在这个信号量上时,调用这个函数会使信号量的值加1,从而使其中的一个线程不再阻塞,选择机制同样是由线程的调度策略决定的。