一、互斥锁(互斥)
使用步骤:
1、定义一个互斥锁变量:pthread_mutex_t mutex;
2、初始化互斥锁:预设互斥锁的初始值:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER(编译时初始化锁位解锁状态)
初始化互斥锁的函数
(a)函数原型
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
· 功能:初始化定义的互斥锁
什么是初始化,就是设置互斥锁所需要的值。
· 返回值
总是返回0,所以这个函数不需要进行出错处理。
· 参数
- mutex:互斥锁,需要我们自己定义。
比如:pthread_mutex_t mutex;
pthread_mutex_t是一个结构体类型,所以mutex实际上是一个结构体变量。
- attr:互斥锁的属性
设置NULL表示使用默认属性,除非我们想要实现一些互斥锁的特殊功能,否则默认属
性就够用了。
3、加锁解锁
pthread_mutex_lock(&mutex)(阻塞加锁)访问临界区加锁操作
pthread_mutex_trylock( &mutex)(非阻塞加锁); pthread_mutex_lock() 类似,不同的是在锁已经被占据时返回 EBUSY 而不是挂起等待。
pthread_mutex_unlock(&mutex): 访问临界区解锁操
4、进程退出时销毁互斥锁
pthread_mutex_destroy
(a)函数原型
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
(b)功能:销毁互斥锁
所谓销毁,说白了就是删除互斥锁相关的数据,释放互斥锁数据所占用的各种内存资源。
(c)返回值:成功返回0,失败返回非零错误号
二、信号量(同步)
线程信号量的使用步骤:
1、定义信号量集合
信号量集合需要我们自己定义,
比如:sem_t sem[3],
线程信号量集合其实就是一个数组,数组每个元素就是一个信号量。
sem[0]:第一个信号量
sem[1]:第二个信号量
sem[2]:第三个信号量
2、初始化集合中的每个信号量
初始化信号量的函数
(a)函数原型
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
· 功能
初始化线程信号量集合中的某个信号量,给它设置一个初始值。
· 返回值
成功返回0,失败返回-1,errno被设置。
注意信号量的错误号不是返回的,而是设置到errno中。
· 参数
- sem:信号量集合中的某个信号量
信号量集合需要我们自己定义,
比如:sem_t sem[3],
线程信号量集合其实就是一个数组,数组每个元素就是一个信号量。
sem[0]:第一个信号量
sem[1]:第二个信号量
sem[2]:第三个信号量
sem_init(&sem[0], int pshared, unsigned int value);
线程信号量集合其实就是自定义的一个数组,而进程信号量集合则是通过semget函数创建的。
我们只要把数组定义为全局变量,所有的线程即可共享使用,不像进程信号量,需要semid
才能实现共享操作。
- pshared:
+ 0:给线程使用
+ !0:可以给进程使用
不过对于进程来说,我们更多的还是使用进程信号量,因为线程信号量用到
进程上时,存在一些不稳定的情况。
- value:初始化值
对于二值信号量来说,要么是1,要么是0。
3、p、v操作
P操作
(a)函数原型
#include <semaphore.h>
int sem_wait(sem_t *sem);//阻塞p操作
· 功能:阻塞p操作集合中某个信号量,值-1
如果能够p操作成功最好,否则就阻塞直到p操作操作成功为止。
· 返回值:成功返回0,失败返回-1,errno被设置。
· 参数:p操作的某个信号量。
比如:sem_wait(&sem[0]);
· sem_wait的兄弟函数
int sem_trywait(sem_t *sem):不阻塞
如果能够p操作就p操作,如果不能p操作就出错返回,不会阻塞。
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
可以设置阻塞时间,如果能够p操作就p操作,不能就阻塞,如果在设置的时间内好没有
p操作成功就是出错返回,不再阻塞。
这两个函数了解即可,不需要掌握,如果你真的用到了,自己举一反三即可搞定。
(b)代码演示
sem_wait(&sem[0]);
4)v操作
(a)函数原型
#include <semaphore.h>
int sem_post(sem_t *sem);
· 功能:对某个信号量进行v操作,v操作不存在阻塞问题。
v操作成功后,信号量的值会+1
· 返回值:成功返回0,失败返回-1,errno被设置。
(b)代码演示
sem_post(&sem[0]);
在实际代码开发中,sem_wait和sem_post是成对出现的。
4、进程结束时,删除线程信号量结合
删除信号量
(a)函数原型
#include <semaphore.h>
int sem_destroy(sem_t *sem);
· 功能:删除某个信号量,把所有信号量都删除后,信号量集合就被销毁。
这与删除进程信号量集合有所不同,对于进程信号量集合来说,只要删除一个信号量,整个集合
即被删除,但是对于线程信号量来说,需要一个一个的删除,当所有信号量都删除完后,集合才被
删除完毕。
三、条件变量
条件变量的使用步骤:
1、定义一个条件变量(全局变量)由于条件变量需要互斥锁的配合,所以还需要定义一个线程互斥锁pthread_cond_t
2、初始化条件变量
函数原型
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
· 功能
初始化条件变量,与互斥锁的初始化类似。
pthread_cond_t cond; //定义条件变量
pthread_cond_init(&cond, NULL); //第二个参数为NULL,表示不设置条件变量的属性。
也可以直接初始化:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//与互斥锁的初始化的原理是一样的
· 返回值:成功返回0,失败返回非零错误号
· 参数
- cond:条件变量
- attr:用于设置条件变量的属性,设置为NULL,表示使用默认属性
3、使用条件变量
2)等待条件的函数
(a)函数原型
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
· 功能:
检测条件变量cond,如果cond没有被设置,表示条件还不满足,别人还没有对cond进行设置,此时
pthread_cond_wait会休眠(阻塞),直到别的线程设置cond表示条件准备好后,才会被唤醒。
· 返回值:成功返回0,失败返回非零错误号
· 参数
- cond:条件变量
- mutex:和条件变量配合使用的互斥锁
(c)pthread_cond_wait的兄弟函数
int pthread_cond_timedwait(pthread_cond_t *restrict cond, \
pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
多了第三个参数,用于设置阻塞时间,如果条件不满足时休眠(阻塞),但是不会一直休眠,
当时间超时后,如果cond还没有被设置,函数不再休眠。
3)设置条件变量的函数
(a)函数原型
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
· 功能
当线程将某个数据准备好时,就可以调用该函数去设置cond,表示条件准备好了,
pthread_cond_wait检测到cond被设置后就不再休眠(被唤醒),线程继续运行,使用别的线程
准备好的数据来做事。
当调用pthread_cond_wait函数等待条件满足的线程只有一个时,就是用pthread_cond_signal
来唤醒,如果说有好多线程都调用pthread_cond_wait在等待时,使用
int pthread_cond_broadcast(pthread_cond_t *cond);
它可以将所有调用pthread_cond_wait而休眠的线程都唤醒。
(b)代码演示
调用pthread_cond_signal去设置条件变量,相当是给pthread_cond_wait发送了一个线程间专
用的信号,通知调用pthread_cond_wait的线程,某某条件满足了,不要再睡了,赶紧做事吧。
4、删除条件变量
删除条件变量
(a)函数原型
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);