互斥锁

声明一把互斥锁

注意:互斥锁是一个全局变量

pthread_mutex_t  mutex;

初始化和释放

// 初始化互斥锁
pthread_mutex_init(&mutex, NULL);
// 线程销毁之后, 再去释放互斥锁
pthread_mutex_destroy(&mutex);

加锁&解锁

pthread_mutex_lock(&mutex):

  • 没有被锁定,是打开的,这个线程可以加锁成功,这个这个锁中会记录是哪个线程加锁成功了

  • 如果被锁定了,其他线程加锁就失败了,这些线程都会阻塞在这把锁上

  • 当这把锁被解开之后,这些阻塞在锁上的线程就解除阻塞了,并且这些线程是通过竞争的方式对这把锁加锁,没抢到锁的线程继续阻塞

pthread_mutex_trylock(&mutex):

  • 如果这把锁没有被锁定是打开的,线程加锁成功

  • 如果锁变量被锁住了,调用这个函数加锁的线程,不会被阻塞,加锁失败直接返回错误号

pthread_mutex_unlock(&mutex):

读写锁

定义:读写锁是互斥锁的升级版,在做读操作的时候可以提高程序的执行效率,如果所有的线程都是做读操作, 那么读是并行的,但是使用互斥锁,读操作也是串行的。

特征:

  • 使用读写锁的读锁锁定了临界区,线程对临界区的访问是并行的,读锁是共享的。

  • 使用读写锁的写锁锁定了临界区,线程对临界区的访问是串行的,写锁是独占的。

  • 使用读写锁分别对两个临界区加了读锁和写锁,两个线程要同时访问者两个临界区,访问写锁临界区的线程继续运行,访问读锁临界区的线程阻塞,因为写锁比读锁的优先级高。

声明一把读写锁

pthread_rwlock_t rwlock;

初始化&释放

// 初始化读写锁
pthread_rwlock_init(&rwlock, NULL);
// 销毁读写锁
pthread_rwlock_destroy(&rwlock)

加锁&解锁

  • pthread_rwlock_rdlock(&rwlock):调用这个函数,如果读写锁是打开的,那么加锁成功;如果读写锁已经锁定了读操作,调用这个函数依然可以加锁成功,因为读锁是共享的;如果读写锁已经锁定了写操作,调用这个函数的线程会被阻塞

  • pthread_rwlock_tryrdlock(&rwlock):调用这个函数,如果读写锁是打开的,那么加锁成功;如果读写锁已经锁定了读操作,调用这个函数依然可以加锁成功,因为读锁是共享的;如果读写锁已经锁定了写操作,调用这个函数加锁失败,对应的线程不会被阻塞,可以在程序中对函数返回值进行判断,添加加锁失败之后的处理动作。

  • pthread_rwlock_wrlock(&rwlock):调用这个函数,如果读写锁是打开的,那么加锁成功;如果读写锁已经锁定了读操作或者锁定了写操作,调用这个函数的线程会被阻塞。

  • pthread_rwlock_trywrlock(&rwlock):调用这个函数,如果读写锁是打开的,那么加锁成功;如果读写锁已经锁定了读操作或者锁定了写操作,调用这个函数加锁失败,但是线程不会阻塞,可以在程序中对函数返回值进行判断,添加加锁失败之后的处理动作。

  • pthread_rwlock_unlock(&rwlock):解锁, 不管锁定了读还是写都可用解锁。

条件变量

声明一个条件变量

pthread_cond_t cond;

初始化和销毁

pthread_cond_init(&cond,NULL)
pthread_cond_destroy(&cond)

wait操作

pthread_cond_wait(&cond,&mutex) 
  • 在阻塞线程时候,如果线程已经对互斥锁 mutex 上锁,那么会将这把锁打开,这样做是为了避免死锁

  • 当线程解除阻塞的时候,函数内部会帮助这个线程再次将这个 mutex 互斥锁锁上,继续向下访问临界区

唤醒操作

// 唤醒阻塞在条件变量上的线程, 至少有一个被解除阻塞
int pthread_cond_signal(pthread_cond_t *cond);
// 唤醒阻塞在条件变量上的线程, 被阻塞的线程全部解除阻塞
int pthread_cond_broadcast(pthread_cond_t *cond);

信号量

声明一个信号量

sem_t sem;

初始化和释放

// 信号量初始化
sem_init(&psem, 0, 5); // 5个生产者可以同时生产
sem_init(&csem, 0, 0); // 消费者线程没有资源, 因此不能消费
// 信号销毁
sem_destroy(&psem);
sem_destroy(&csem);

wait操作

  • sem_wait(&sem):当线程调用这个函数,并且 sem 中的资源数 >0,线程不会阻塞,线程会占用 sem 中的一个资源,因此资源数 - 1,直到 sem 中的资源数减为 0 时,资源被耗尽,因此线程也就被阻塞了。

  • sem_trywait(&sem):当线程调用这个函数,并且 sem 中的资源数 >0,线程不会阻塞,线程会占用 sem 中的一个资源,因此资源数 - 1,直到 sem 中的资源数减为 0 时,资源被耗尽,但是线程不会被阻塞,直接返回错误号,因此可以在程序中添加判断分支,用于处理获取资源失败之后的情况。

唤醒操作

sem_post(&sem)

调用该函数会将 sem 中的资源数 +1,如果有线程在调用 sem_wait、sem_trywait、sem_timedwait 时因为 sem 中的资源数为 0 被阻塞了,这时这些线程会解除阻塞,获取到资源之后继续向下运行。

查看资源个数

// 查看信号量 sem 中的整形数的当前值, 这个值会被写入到sval指针对应的内存中
// sval是一个传出参数
int sem_getvalue(sem_t *sem, int *sval);

通过这个函数可以查看 sem 中现在拥有的资源个数,通过第二个参数 sval 将数据传出,也就是说第二个参数的作用和返回值是一样的。