疯狂java


您现在的位置: 疯狂软件 >> 新闻资讯 >> 正文

线程的同步与互斥:条件变量


 

   

  条件变量(Condition Variable)

  条件变量的作用:

  虽然互斥锁实现了线程之间的互斥,但是互斥锁也有不足之处,它只能表示两种状态:上锁和非上锁。但是假如有线程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 上等待的所有线程。使用这种方法时,瞬间唤醒的线程数过多,资源请求过大,容易导致系统不稳定。

  条件变量的使用:

  条件变量搭配互斥锁可以实现单生产者-单消费者模型,

  信号量(Semaphore)

  对信号量的理解:

  1.信号量是一种特殊的变量,它只能取自然数并且只支持两种操作。

  2.具有多个整数值的信号量称为通用信号量,只取 1 和 0 两个数值的称为二元信号量,这里我只讨论二元信号量。

  3.信号量的两个操作:P(passeren,传递-进入临界区)、V(vrijgeven,释放-退出临界区)

  假设现有信号量sv,

  P操作:如果sv的值大于0,就将其减1;如果sv的值为0,就将当前线程挂起;

  V操作:如果有其他线程因为等待sv而被挂起,则将其唤醒;如果没有,就将sv加1.

  综上:

  可以理解信号量是一种计数器,P操作就是将计数器的值减1,执行一次P操作意味着请求一次资源,即当前可以获得资源;V操作就是将计数器的值加1,即表示当前有资源可以使用。

  信号量的接口函数:

  1.初始化:

  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);

  4.V操作:

  int sem_post(sem_t *sem);

  信号量的使用: