互斥
概念
互斥:在多线程中对临界资源(公共资源)的排他性访问。
互斥机制 >>互斥锁 >>保证临界资源的访问控制。
通过使用互斥锁,可以确保某一时刻只有一个线程能够访问临界资源,从而避免竞争条件(race conditions)的发生
pthread_mutex_t mutex;
互斥锁类型 互斥锁变量 内核对象
框架:
定义互斥锁 >>初始化锁 >>加锁 >>解锁 >>销毁
**** *** ***
定义
pthread_mutex_t mutex;
定义变量(一般定义为全局变量)
初始化锁
int pthread_mutex_init(pthread_mutex_t *mutex,
const pthread_mutexattr_t *attr);
- 功能
将已经定义好的互斥锁初始化。
- 参数
mutex 要初始化的互斥锁
atrr 初始化的值,一般是NULL表示默认锁
- 返回值
成功 0 失败 非零
加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
- 功能
用指定的互斥锁开始加锁代码
加锁后的代码到解锁部分的代码属于原子操作(最小操作、不可再分割的操作),
在加锁期间其他进程/线程都不能操作该部分代码(这部分代码应该尽可能短)
如果该函数在执行的时候,mutex已经被其他部分使用则代码阻塞。
- 参数
mutex 用来给代码加锁的互斥锁
- 返回值
成功 0 失败 非零
加锁时机
- 访问共享资源时
在任何线程需要读写共享资源(如全局变量、共享数据结构、文件等)之前,应该先上锁。这样可以防止其他线程同时访问这个资源,导致数据竞争或不一致的问题。
例如:在一个银行账户系统中,多个线程同时更新账户余额时,应在修改余额之前锁定。
- 进入临界区时
临界区是指代码中可能被多个线程并发访问,且访问时需要互斥的部分。临界区通常是需要同步的代码块,在进入临界区前应上锁。
例如:在生产者-消费者模型中,生产者和消费者访问同一个缓冲区时,需要在进入缓冲区的操作代码之前上锁。
- 检查或修改共享状态时
如果线程需要检查或修改某个共享状态(如队列的长度、任务的状态等),应该在检查或修改之前上锁,以确保其他线程不会在中间更改状态。
例如:在线程池中,当线程检查是否有任务可用时,应先上锁以确保状态一致
解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
- 功能
将指定的互斥锁解锁。
解锁之后代码不再排他访问,一般加锁解锁同时出现。
- 参数
用来解锁的互斥锁
- 返回值
成功 0 失败 非零
解锁时机
- 离开临界区时
当线程完成了对共享资源的访问或完成了临界区的操作,应立即解锁。这样其他等待的线程可以获取锁并继续执行,避免死锁或资源饥饿。
例如:在修改完共享数据之后,线程应立即解锁以释放互斥锁,让其他线程可以访问共享数据。
- 操作完成时
如果锁是为了保护某个操作的原子性(即操作不可分割),那么在操作完成后应解锁。
例如:如果线程锁定了一个文件进行读写操作,在操作完成后应解锁,以便其他线程可以访问文件。
- 上锁:在访问共享资源、进入临界区、或检查/修改共享状态之前上锁
- 解锁:在离开临界区、操作完成后立即解锁
销毁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
- 功能
使用互斥锁完毕后需要销毁互斥锁
- 参数
mutex 要销毁的互斥锁
- 返回值
成功 0 失败 非零
trylock
int pthread_mutex_trylock(pthread_mutex_t *mutex);
- 功能
类似加锁函数效果,唯一区别就是不阻塞
- 参数
mutex 用来加锁的互斥锁
- 返回值
成功 0 失败 非零
当锁已经被占用时,通常返回 E_AGAIN
同步
线程的同步
同步 :有一定先后顺序的对资源的排他性访问。
同步产生原因:互斥锁可以控制排他访问但没有次序。
linux下的线程同步 >>信号量机制 >>semaphore.h posix
sem_open();
信号量的分类:
1、无名信号量 ——线程间通信
2、有名信号量 ——进程间通信
框架:
信号量的定义>>信号量的初始化 >>信号量的PV操作>>信号量的销毁
信号量的定义
semaphore
sem_t sem;
信号量的类型 信号量的变量
信号量的初始化
int sem_init(sem_t *sem, int pshared, unsigned int value);
- 功能
将已经定义好的信号量赋值。
- 参数
sem 要初始化的信号量
pshared = 0 ;表示线程间使用信号量
!=0 ;表示进程间使用信号量
value 信号量的初始值,一般无名信号量
都是二值信号量,0 1
0 表示红灯,进程暂停阻塞
1 表示绿灯,进程可以通过执行
返回值:成功 0 失败 -1;
信号量的PV 操作
P :申请资源——申请一个二值信号量 (0or1)
V :释放资源——释放一个二值信号量
P操作对应函数 :sem_wait();
V操作对应函数 :sem_post();
sem_wait()
int sem_wait(sem_t *sem);
- 功能
判断当前sem信号量是否有资源可用。
如果sem有资源(==1),则申请该资源,程序继续运行
如果sem没有资源(==0),则线程阻塞等待,一 旦有资源
则自动申请资源并继续运行程序。
注意:sem 申请资源后会自动执行 sem = sem - 1;(可用作计数信号量)
- 参数
sem 要判断的信号量资源
- 返回值
成功 0 失败 -1
sem_post()
int sem_post(sem_t *sem);
- 功能
函数可以将指定的sem信号量资源释放
并默认执行,sem = sem+1;
线程在该函数上不会阻塞。
- 参数
sem 要释放资源的信号量
- 返回值
成功 0 失败 -1;
信号量的销毁
int sem_destroy(sem_t *sem);
- 功能
使用完毕将指定的信号量销毁
- 参数
sem要销毁的信号量
- 返回值
死锁的产生
产生死锁的原因
主要是:
(1) 因为系统资源不足。
(2) 进程运行推进的顺序不合适。
(3) 资源分配不当等。
如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则
就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。
产生死锁的四个必要条件
(1) 互斥条件:一个资源每次只能被一个进程(线程)使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。