环己三烯的冬眠舱

天天网抑云,偶尔读点书。

0%

pthread.h 库(下)

线程同步

互斥量:pthread_mutex系列

  • int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

初始化一个互斥量,即第一个参数指向的mutex。一个互斥量必须初始化了之后才能用。

  • int pthread_mutex_destroy(pthread_mutex_t *mutex);

顾名思义,就是删除一个互斥量,释放它的内存。

  • int pthread_mutex_lock(pthread_mutex_t *mutex);
  • int pthread_mutex_unlock(pthread_mutex_t *mutex);

给互斥量上锁和解锁。如果需要上锁的互斥量已经被别的线程锁住了,那调用pthread_mutex_lock()的线程就会一直被阻塞直到那个互斥量被解锁,然后这个线程再获得锁。上面三个函数都是成功返回0,否则返回错误编号。如果不希望线程被阻塞,那可以用下面这个函数:

  • int pthread_mutex_trylock(pthread_mutex_t *mutex);

这个函数会尝试对互斥量加锁,如果锁没被锁上,它就锁上这个锁;如果锁已经被锁住了,那么这个函数就会返回EBUSY。这个函数在避免死锁这件事上有很大帮助(如果不能取得全部的一系列锁,不是干等着而是把已经取得的锁都释放掉,避免跟另一个线程死锁)。

  • int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict tsptr);

在某个时间前阻塞等待获得锁,超过这个时间就返回错误码ETIMEDOUT

读写锁:pthread_rwlock系列

读写锁与互斥量类似,但是它支持更高的并行性。它有三种状态:读模式下加锁、写模式下加锁和不加锁。当读写锁是写加锁时,无法被别的线程读、写加锁;当读写锁是读加锁时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是任何希望以写模式对此锁进行加锁的线程都会阻塞。读写锁非常适合对数据结构读的次数远大于写的情况。

  • int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
  • int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

这两个函数分别用于初始化一个读写锁和销毁一个读写锁以释放它的内存。如果成功,返回0;否则,返回错误编号。

  • int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
  • int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
  • int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

这三个函数分别对一个读写锁进行读加锁、写加锁和解锁操作。如果成功,返回0;否则,返回错误编号。

  • int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
  • int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
  • int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict tsptr);
  • int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict tsptr);

就是读写锁版的trylock()timedlock()

条件变量:pthread_cond系列

  • int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
  • int pthread_cond_destroy(pthread_cond_t *cond);

这两个函数分别用于初始化和销毁一个条件变量。如果成功,返回0;否则,返回错误编号。

  • int pthread_cond_wait(pthread_cond_t *restreict cond, pthread_mutex_t *restrict mutex);
  • int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict tsptr);

这两个函数用于等待条件变量变为真。而第二个函数则是带有超时的版本。这两个函数都需要自带一个锁上的互斥量,这是为了保证检查条件变量到线程进入休眠等待条件改变这两个操作之间的操作是原子的。因为发信号的接收信号的进程往往会有一些公共的资源(比如生产者-消费者模型,会有一段任务队列是公用的),为了保证这些线程在访问这些公共资源的时候是原子性的,所以需要上锁。那么就有可能发生这样的情况:

消费者获得锁,然后去读取任务队列,发现是空的,所以要等待生产者生产出任务,然后发送信号;

而生产者在向任务队列中写入任务前也需要获得锁,这就造成了一个死锁现象。

为了避免这种情况,消费者在进入睡眠等待之前,要释放锁,让生产者去获得锁并生产出任务;生产者生产出任务并唤醒消费者后,消费者要再次获得锁并读取任务队列。

那么一来二去的,人们就发现还不如直接把这些上锁解锁的操作直接集成进pthread_cond_wait()里来的方便,于是这个函数就多了一个互斥量的参数了。

就这么干说有点抽象,可以看看这篇文章,写的相当详细,赞。

  • int pthread_cond_signal(pthread_cond_t *cond);
  • int pthread_cond_broadcast(pthread_cond_t *cond);

这两个函数用于向某个信号量发出条件满足的信号,第一个函数至少能唤醒一个等待该条件的线程,而第二个能唤醒所有等待该条件的所有线程。如果成功,返回0;否则,返回错误编号。

还有一个要注意的点是我们要用while而不是if来判断条件,因为如果有多个消费者存在的话,可能某个消费者被唤醒后还没来得及获得锁,任务就已经被其它消费者处理完了。

屏障:pthread_barrier系列

屏障是用户协调多个线程并行工作的同步机制。屏障允许每个线程等待,直到所有合作的线程都达到某一点,然后从该点继续执行。pthread_join()就是一种屏障,允许一个线程等待,直到另一个线程退出。但是屏障对象的概念更广,它们允许任意数量的线程等待,直到所有的线程完成处理工作,而线程不需要退出,所有线程达到屏障后可以接着工作。

  • int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned int count);
  • int pthread_barrier_destroy(pthread_barrier_t *barrier);

这两个函数分别用于屏障的初始化和销毁,如果成功,返回0;否则,返回错误编号。在初始化函数中,第三个参数count可以指定允许所有线程继续运行前,必须到达屏障的线程数目。

  • int pthread_barrier_wait(pthread_barrier_t *barrier);

这个函数表示线程已经完成工作,正在等待其他线程赶上来。如果成功,返回0或者PTHREAD_BARRIER_SERIAL_THREAD;否则,返回错误编号。只有第一个成功返回wait()会返回PTHREAD_BARRIER_SERIAL_THREAD,其余都返回0。这样特殊一点的线程就可以作为主线程工作在其他所有线程已完成的工作上。

一旦到达屏障计数值,而且线程处于非阻塞状态,屏障就可以被重用。如果需要改变计数值,就需要destroy()后重新init()