1. 互斥锁
1.1 创建
pthread_mutex_t mutex; // 创建一个互斥锁,需要定义为全局变量
1.2 初始化
#include <pthread.h> int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
1.3 加锁
#include <pthread.h> int pthread_mutex_lock(pthread_mutex_t *mutex); // 获取不到会阻塞
1.4 解锁
#include <pthread.h> int pthread_mutex_unlock(pthread_mutex_t *mutex);
以下行为会报错:
- 对处于未锁定的互斥锁进行解锁操作
- 解锁其他线程锁定的互斥锁
1.5 非阻塞加锁
#include <pthread.h> int pthread_mutex_trylock(pthread_mutex_t *mutex);
参数 mutex 指向目标互斥锁,成功返回 0,失败返回一个非 0 值的错误码,如果目标互斥锁已经被其它线程锁住,则调用失败返回 EBUSY
1.6 销毁
#include <pthread.h> int pthread_mutex_destroy(pthread_mutex_t *mutex);
- 不能销毁没有解锁的互斥锁
- 不能销毁没有初始化的互斥锁
1.7 互斥锁属性
1.7.1 初始化和销毁
#include <pthread.h> int pthread_mutexattr_destroy(pthread_mutexattr_t *attr); int pthread_mutexattr_init(pthread_mutexattr_t *attr);
1.7.2 类型
#include <pthread.h> int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type); int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);
- PTHREAD_MUTEX_NORMAL:一种标准的互斥锁类型,不做任何的错误检查或死锁检测。如果线程试图对已经由自己锁定的互斥锁再次进行加锁,则发生死锁;互斥锁处于未锁定状态,或者已由其它线程锁定,对其解锁会导致不确定结果。
PTHREAD_MUTEX_ERRORCHECK:此类互斥锁会提供错误检查。譬如这三种情况都会导致返回错误:线程试图对已经由自己锁定的互斥锁再次进行加锁(同一线程对同一互斥锁加锁两次),返回错误;线程对由其它线程锁定的互斥锁进行解锁,返回错误;线程对处于未锁定状态的互斥锁进行解锁,返回错误。这类互斥锁运行起来比较慢,因为它需要做错误检查,不过可将其作为调试工具,以发现程序哪里违反了互斥锁使用的基本原则。
PTHREAD_MUTEX_RECURSIVE:此类互斥锁允许同一线程在互斥锁解锁之前对该互斥锁进行多次加锁,然后维护互斥锁加锁的次数,把这种互斥锁称为递归互斥锁,但是如果解锁次数不等于加速次数,则是不会释放锁的;
- 所以,如果对一个递归互斥锁加锁两次,然后解锁一次,那么这个互斥锁依然处于锁定状态,对它再次进行解锁之前不会释放该锁。
- PTHREAD_MUTEX_DEFAULT : 此类互斥锁提供默认的行为和特性 。 使 用
PTHREAD_MUTEX_INITIALIZER 初 始 化 的互斥锁 , 或者调用参数arg为NULL 的pthread_mutexattr_init()函数所创建的互斥锁,都属于此类型。此类锁意在为互斥锁的实现保留
- 最大灵活性, Linux 上 , PTHREAD_MUTEX_DEFAULT 类型互斥锁的行为与PTHREAD_MUTEX_NORMAL 类型相仿。
2. 条件变量
条件变量通常是和互斥锁一起使用的
2.1 创建
pthread_cond_t cond; // 定义为全局变量,这样多个线程才能够访问
2.2 初始化
#include <pthread.h> int pthread_cond_destroy(pthread_cond_t *cond); int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
2.3 通知等待条件变量的线程
#include <pthread.h> int pthread_cond_broadcast(pthread_cond_t *cond); // 唤醒所有线程 int pthread_cond_signal(pthread_cond_t *cond); // 只能唤醒一个线程
2.4 等待条件变量
调用pthread_cond_wait()函数时,调用者把互斥锁传递给函数,函数会自动把调用线程放到等待条件的线程列表上,然后将互斥锁解锁;当 pthread_cond_wait()被唤醒返回时,会再次锁住互斥锁。
#include <pthread.h> int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
mutex:参数 mutex 是一个 pthread_mutex_t 类型指针,指向一个互斥锁对象;前面开头便给大家介绍了,条件变量通常是和互斥锁一起使用,因为条件的检测(条件检测通常是需要访问共享资源的)是在互斥锁的保护下进行的,也就是说条件本身是由互斥锁保护的。
3. 自旋锁
互斥锁是基于自旋锁来实现的,所以自旋锁相较于互斥锁更加底层。
互斥锁在无法获取到锁时会让线程陷入阻塞等待状态;而自旋锁在无法获取到锁时,将会在原地“自旋”等待。
互斥锁和自旋锁区别:
实现方式上的区别:互斥锁是基于自旋锁而实现的,所以自旋锁相较于互斥锁更加底层;
销上的区别:获取不到互斥锁会陷入阻塞状态(休眠),直到获取到锁时被唤醒;而获取不到自旋锁会在原地“自旋”,直到获取到锁;休眠与唤醒开销是很大的,所以互斥锁的开销要远高于自旋锁、自旋锁的效率远高于互斥锁;但如果长时间的“自旋”等待,会使得 CPU 使用效率降低,故自旋锁不适用于等待时间比较长的情况。
使用场景的区别:自旋锁在用户态应用程序中使用的比较少,通常在内核代码中使用比较多;因为自旋锁可以在中断服务函数中使用,而互斥锁则不行,在执行中断服务函数时要求不能休眠、不能被抢占(内核中使用自旋锁会自动禁止抢占),一旦休眠意味着执行中断服务函数时主动交出了CPU 使用权,休眠结束时无法返回到中断服务函数中,这样就会导致死锁!
3.1 创建
pthread_spin_t spinlock;
3.2 初始化
#include <pthread.h> int pthread_spin_destroy(pthread_spinlock_t *lock); int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
pshard表示自旋锁的进程共享属性:
- PTHREAD_PROCESS_SHARED: 该自旋锁可在多个进程中的线程之间共享
- PTHREAD_PROCESS_PRIVATE: 只有在本进程的线程才能使用
3.3 加锁解锁
#include <pthread.h> int pthread_spin_lock(pthread_spinlock_t *lock); int pthread_spin_trylock(pthread_spinlock_t *lock); // 如果未能获取到锁,就立刻返回错误,错误码为 EBUSY int pthread_spin_unlock(pthread_spinlock_t *lock);
4. 读写锁
读写锁的规则:
- 当读写锁处于写加锁状态时,在这个锁被解锁之前,所有试图对这个锁进行加锁操作(不管是以读模式加锁还是以写模式加锁)的线程都会被阻塞。
- 当读写锁处于读加锁状态时,所有试图以读模式对它进行加锁的线程都可以加锁成功;但是任何以写模式对它进行加锁的线程都会被阻塞,直到所有持有读模式锁的线程释放它们的锁为止。
所以,读写锁非常适合于对共享数据读的次数远大于写的次数的情况。
4.1 创建
pthread_rwlock_t rwlock;
4.2 初始化
#include <pthread.h> int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);
4.3 读写锁上锁和解锁
#include <pthread.h> int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
4.4 非阻塞方式加锁
#include <pthread.h> int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
参数 rwlock 指向需要加锁的读写锁,加锁成功返回 0,加锁失败则返回 EBUSY。
4.5 属性
读写锁只有一个属性,那便是进程共享属性
#include <pthread.h> int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr); int pthread_rwlockattr_init(pthread_rwlockattr_t *attr); int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr, int *pshared); int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);
pshared:
- PTHREAD_PROCESS_SHARED: 该读写锁可在多个进程中的线程之间共享
- PTHREAD_PROCESS_PRIVATE: 只有在本进程的线程才能使用
疑问
条件变量哪里为什么要用while?, 而不用if判断
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<pthread.h> #include<string.h> int number = 0; pthread_mutex_t mutex; pthread_cond_t cond; void *thread_func(void *arg) { int id = (int)arg; printf("消费者.......\n"); for (;;) { // 获取互斥锁(阻塞方式) pthread_mutex_lock(&mutex); // 获得条件变量(阻塞) while(number <= 0) {pthread_cond_wait(&cond, &mutex);} // ???????????? number--; printf("消费者%d--%d\n", id, number); // 释放互斥锁 pthread_mutex_unlock(&mutex); } } int main(int argc, char **argv) { int i = 0; int ret = 0; pthread_t tid_lis[5]; // 初始化互斥锁 pthread_mutex_init(&mutex, NULL); // 初始化条件变量 pthread_cond_init(&cond, NULL); printf("debug\n"); // 创建线程 for (i=0;i<5;i++) { ret = pthread_create(&tid_lis[i], NULL, thread_func, (void *)(i+1)); if (ret) { perror("pthread_create error"); exit(-1); } } printf("生产者......\n"); // 主线程作为生产者 for(;;) { pthread_mutex_lock(&mutex); // 获取锁 number++; printf("生产者++\n"); pthread_mutex_unlock(&mutex); // 释放锁 pthread_cond_signal(&cond); // 发送“信号” //sleep(1); } pthread_mutex_destroy(&mutex); return 0; }