- 互斥锁 MUTEXS
- 信号量 SEMAPHORES
- 读写锁 READ-WRITE LOCKS
互斥锁 MUTEXS
保护临界区
以确保只有一个线程或者进程可以同时执行临界区内的代码
目的:互斥的访问共享资源,避免并发访问出现的竞争
核心思想:进入临界区之前先获取锁,退出临界区之后释放锁
临界区保护代码示例:
//定义互斥锁
pthread_mutex_t mutex; //互斥锁类型
pthread_mutex_init(&mutex, NULL); //创建并初始化
//加锁
pthread_mutex_lock(&mutex);
//临界区代码
//对共享资源进行操作
//解锁
pthread_mutex_unlock(&mutex);
//销毁互斥锁
pthread_mutex_destroy(&mutex);
任意时刻只有一个线程或者进程可以进入临界区,保证了共享资源的安全访问
下面是一个更为直接的例子
lock_the_mutex(…); //获取互斥锁
critical region; //在临界区执行操作
unlock_the_mutex(…); //释放互斥锁
互斥锁的声明
#include <pthread.h>
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
如果互斥锁变量是静态分配的,我们可以将其初始化为常量 PTHREAD_MUTEX_INITIALIZER
这样就可以直接使用lock作为互斥锁,并且他被自动初始化为一个可用的互斥锁
#include <pthread.h>
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int main() {
// 在代码中使用互斥锁
pthread_mutex_lock(&lock);
// 临界区
pthread_mutex_unlock(&lock);
return 0;
}
使用静态初始化的互斥锁可以简化代码(更方便)
但是该互斥锁无法动态销毁(避免在不再需要的时候重复使用静态初始化的互斥锁)
互斥锁的操作函数示例
#include <pthread.h>
//以下的mptr都是指向互斥锁变量的指针
//操作成功返回0,错误返回错误码
//初始化互斥锁
//attr(通常设置为null)指向互斥锁属性
int pthread_mutex_init(pthread_mutex_t *mptr, const pthread_mutexattr_t *attr);
//加锁
//如果当前的互斥锁被其他线程锁定,则该函数会阻塞并且等待,直到互斥锁可用
int pthread_mutex_lock(pthread_mutex_t *mptr);
//解锁
//只有有锁的线程才能解锁
int pthread_mutex_unlock(pthread_mutex_t *mptr);
//销毁互斥锁
//销毁之前确保已经被解锁
int pthread_mutex_destroy(pthread_mutex_t *mptr);
pthread_mutex_trylock函数
可以获取互斥锁,并根据返回值判断是否获取成功
返回ebusy(错误码) ,则说明互斥锁已经被其他线程占用,可以等待一段时间后重试或者进行其他操作
生产者消费者问题:
涉及一个或多个生产者生成数据项,然后由一个或多个消费者处理
这中情况下我们可以
使用互斥锁
使用条件变量
#include <pthread.h>
#define MAX_BUFFER_SIZE 10
//共享数据定义为buff
int buff[MAX_BUFFER_SIZE];
int count = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void *producer(void *arg) {
while (true) {
// 生成数据项
pthread_mutex_lock(&mutex);
// 将项添加到 buff
count++;
if (count == 1) {
// 如果缓冲区为空,则通知消费者
pthread_cond_signal(&cond);
}
pthread_mutex_unlock(&mutex);
}
}
void *consumer(void *arg) {
while (true) {
pthread_mutex_lock(&mutex);
while (count == 0) {
// 等待项目可用
pthread_cond_wait(&cond, &mutex);
}
// 从 buff 中消费项
count--;
pthread_mutex_unlock(&mutex);
}
}
int main() {
// 创建生产者和消费者线程
// 加入线程
return 0;
}
信号量 SEMAPHORES
信号量是一种原语,用于在各个进程之间给定进程中的各个线程之间提供同步
//操作:
Create a semaphore: //创建一个信号量,指定初始值。
Wait for a semaphore: //等待一个信号量,如果值为0则阻塞等待。
Post to a semaphore: //发送信号到一个信号量,增加信号量的值。
一个二进制信号量可以像互斥锁一样用于互斥操作
#include <semaphore.h>
//初始化二进制信号量
//sem 指向信号量的指针
//shared 指定信号量是否在不同进程间共享,如果为0,则信号量在进程的线程之间共享,否则在进程之间共享
//value 设置信号量的初始值
int sem_init(sem_t * sem, int shared, unsigned int value);
/*
sem_wait;
sem_post;
*/
//销毁信号量,释放相关资源
int sem_destroy(sem_t * sem);
- 如果一个内存信号量在单个进程的线程之间共享,那么该信号量具有进程持久性,并在进程终止时消失
- 如果一个内存信号量在不同进程之间共享,那么该信号量必须存储在共享内存中,并且只要共享内存存在,信号量就会一直存在
几个操作信号量的函数
sem_wait
用于测试指定信号量的值,
大于0 则减一,函数立即返回
等于0,调用线程则会被设置为睡眠状态,直到他大于0,然后减一,并返回函数
int sem_wait(sem_t *sem);
sem_trywait
如果信号量为0,不会进入睡眠状态
int sem_trywait(sem_t *sem);
sem_post
一个线程完成对信号量的使用时调用
将信号量的值加1,并且唤醒正在等待信号值变为整数的任何线程
int sem_post(sem_t *sem);
sem_getvalue
valp 所指向整数的当前信号量值
如果当前信号量被锁定,则返回值要么是0,要么是一个负数
其绝对值是正在等待信号量解锁的线程数
int sem_getvalue(sem_t *sem, int *valp);
读写锁 READ-WRITE LOCKS
同步原语
允许多个线程同时读取共享资源,但是只允许一个线程写入
规则:
1 没有线程持有读写锁写入时,可以有任意数量的线程同时持有读写锁进行读取,允许多个线程并发的读取,并且不会被阻塞
2 当没有其他线程读取或者写入时,线程才能进行写入,避免对共享数据并发的修改
这种规则适用于,读取频率远远高于写入频率 的情况,读写锁比互斥锁效率更高
#include <pthread.h>
//读写锁的数据类型是pthread_rwlock_t
//分配了此类型的静态变量,可以通过将constat PTHREAD_RWLOCK_INITIALIZER赋值进行初始化
int pthread_rwlock_init(pthread_rwlock_t *rwptr, const pthread_rwlockattr_t *attr);
//获取一个读锁,读写锁当前由写者持有,则阻塞调用线程
int pthread_rwlock_rdlock(pthread_rwlock_t *rwptr);
//获取一个写锁,如果现在被其他写者持有,或者一个或多个线程持有,则阻塞进程
int pthread_rwlock_wrlock(pthread_rwlock_t *rwptr);
//释放一个读锁或者写锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwptr);
//成功返回0,错误返回正数值
tryrdlock
trywrlock
尝试获取读锁或者写锁
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwptr);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwptr);
如果锁无法被获取,则返回ebusy错误(不会将调用线程设置为睡眠状态)
成功返回0,失败返回positive Exxx value
当线程不再需要读写锁时,可以调用函数来销毁它
int pthread_rwlock_destroy(pthread_rwlock_t *rwptr);
成功返回0,失败返回positive Exxx value
本篇文章为自学笔记,全部内容为笔者个人理解,如有缺漏或错误,请帮忙评论留言指正,感谢大家!!!