4 多线程
理论基础
- 线程也是为了实现多任务并发执行,能实现多个阻塞任务同时执行
- 线程(LWP)是粒度更小的任务执行单元
- 进程是资源分配的基本单位,线程是任务器进行任务调度的最小单位
- 一个进程可以有多个线程,同一个进程中的多个线程共享进程的资源
- 线程的切换开销较小
- 由于多个线程使用同一个进程的资源,会导致每个进程使用资源时产生资源抢占问题,没有多进程安全
- 每个进程至少有一个线程:主线程
- 主线程结束后,整个进程也就结束了
- 多个线程执行没有先后顺序,按时间片轮询上下文切换的方式进行
多线程实现
C库没有提供多线程相关操作,对多线程编程需要依赖第三方库
<pthread.h>,编译时加上-lpthread选项链接库
创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);- 参数:
- 用于接收线程号的变量的地址
- 确定线程属性的结构体的首地址,一般传
nullptr表示默认属性 - 回调函数,是一个函数指针,作为线程体的执行函数,自定义
- 参数3的参数,没有参数传
nullptr
- 返回值:成功返回0,失败返回一个错误码(线程库中定义)
- 参数:
获取线程号
pthread_t pthread_self(void):获取当前线程的线程号- 返回值:调用线程的线程号,不会失败
退出线程
void pthread_exit(void *retval);:退出当前线程- 参数:退出时的状态,一般传
nullptr
- 参数:退出时的状态,一般传
回收线程资源
int pthread_join(pthread_t thread, void **retval);:阻塞回收线程资源- 参数:
- 要回收的线程的线程号
- 线程退出时的状态,一般传
nullptr
- 返回值:成功返回0,失败返回一个错误码(线程库中定义)
- 参数:
int pthread_detach(pthread_t thread);:将指定线程设置为分离态,分离态的线程退出后资源由系统自动回收,非阻塞- 参数:要设置为分离态的线程
- 返回值:成功返回0,失败返回一个错误码(线程库中定义)
线程同步互斥机制
- 同一个进程的多个线程共享进程的资源,被共享的资源为临界资源
- 多个线程对公共资源的抢占问题,访问临界资源的代码段为临界区
- 多个线程抢占公共资源的现象为竞态
- 同步互斥机制解决资源抢占问题
互斥锁
- 互斥锁的本质也是一个特殊的临界资源,该临界资源被某个线程拥有后其他线程不能拥有该资源,直到该资源被线程释放
- 一个互斥锁同一时刻只能被一个线程拥有
API:
pthread_mutex_t mutex;:定义一个互斥锁变量,即创建了一个互斥锁- 初始化互斥锁:
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);:动态初始化- 参数:
- 互斥锁的地址
- 互斥锁的属性,一般传
nullptr
- 返回值:成功返回0,失败返回错误码(线程库中定义)
- 参数:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;:静态初始化
int pthread_mutex_lock(pthread_mutex_t *mutex);:获取资源,若互斥锁已经被其他线程锁定,会阻塞- 参数:互斥锁的地址
- 返回值:成功返回0,失败返回错误码(线程库中定义)
int pthread_mutex_unlock(pthread_mutex_t *mutex);:释放资源- 参数:互斥锁的地址
- 返回值:成功返回0,失败返回错误码(线程库中定义)
int pthread_mutex_destroy(pthread_mutex_t *mutex);:销毁互斥锁- 参数:互斥锁的地址
- 返回值:成功返回0,失败返回错误码(线程库中定义)
#include <myHead.h>
using namespace std;
int num = 120; //临界资源
pthread_mutex_t mutex; //创建互斥锁
void* task1(void* arg) {
while (true) {
sleep(1);
//临界区
pthread_mutex_lock(&mutex);
num -= 10;
printf("task1 -10 : num = %d\n", num);
pthread_mutex_unlock(&mutex);
}
}
void* task2(void* arg) {
while (true) {
sleep(1);
pthread_mutex_lock(&mutex);
num += 10;
printf("task2 +10 : num = %d\n", num);
pthread_mutex_unlock(&mutex);
}
}
int main(int argc, char* argv[]) {
pthread_mutex_init(&mutex, nullptr); //初始化互斥锁
pthread_t tid1, tid2;
if (pthread_create(&tid1, nullptr, task1, nullptr) != 0) {
fputs("tid1 creaete error\n", stdout);
return -1;
}
if (pthread_create(&tid2, nullptr, task2, nullptr) != 0) {
fputs("tid2 creaete error\n", stdout);
return -1;
}
pthread_mutex_destroy(&mutex); //销毁互斥锁
pthread_join(tid1, nullptr);
pthread_join(tid2, nullptr);
return 0;
}
无名信号量
- 线程同步:多个线程执行有先后顺序,不会出现抢占临界资源的现象
- 同步机制常用于生产者消费者模型:消费者线程要想执行必须先执行生产者线程,多个任务有顺序执行
- 本质上也是一个特殊的临界资源,内部维护一个整数,某个线程要执行前先申请信号量的资源
- 若信号量为0,阻塞等待
- 若信号量为正数,线程获得资源,信号量减少
API:
头文件:
<semaphore.h>
sem_t sem;:定义一个无名信号量类型,即创建了一个无名信号量int sem_init(sem_t *sem, int pshared, unsigned int value);:初始化无名信号量- 参数:
- 无名信号量的地址
- 判断进程还是线程间的同步
- 0:线程间的同步
- 非零:进程间的同步,需要在共享内存段中使用
- 无名信号量的初始值
- 返回值:成功返回0,失败返回-1并置位错误码
- 参数:
int sem_wait(sem_t *sem);:阻塞申请无名信号量的资源,成功后无名信号量--(P操作)- 参数:无名信号量地址
- 返回值:成功返回0,失败返回-1并置位错误码
int sem_post(sem_t *sem);:释放无名信号量资源,成功后无名信号量++(V操作)- 参数:无名信号量地址
- 返回值:成功返回0,失败返回-1并置位错误码
int sem_destroy(sem_t *sem);:销毁无名信号量- 参数:无名信号量地址
- 返回值:成功返回0,失败返回-1并置位错误码
#include <myHead.h>
using namespace std;
sem_t sem; //创建无名信号量
//生产者线程
void* task1(void* arg) {
int k = 5;
while (k--) {
sleep(1);
fputs("生产了一张5090\n", stdout);
sem_post(&sem);
}
pthread_exit(nullptr);
}
//消费者线程
void* task2(void* arg) {
int k = 5;
while (k--) {
sem_wait(&sem);
fputs("买了一张5090\n", stdout);
}
pthread_exit(nullptr);
}
int main(int argc, char* argv[]) {
sem_init(&sem, 0, 0); //初始化无名信号量
pthread_t tid1, tid2;
if (pthread_create(&tid1, nullptr, task1, nullptr) != 0) {
fputs("tid1 creaete error\n", stdout);
return -1;
}
if (pthread_create(&tid2, nullptr, task2, nullptr) != 0) {
fputs("tid2 creaete error\n", stdout);
return -1;
}
sem_destroy(&sem); //销毁无名信号量
pthread_join(tid1, nullptr);
pthread_join(tid2, nullptr);
return 0;
}
条件变量
- 条件变量本质也是一个临界资源,维护了一个队列,当消费者线程要执行时,先进入队列等待生产者的唤醒,生产者执行完后唤醒消费者执行;实现了一个生产者线程和多个消费者线程的同步关系
- 多个消费者在进入休眠队列的过程是互斥的,需要使用互斥锁实现互斥操作
API:
pthread_cond_t cond;:定义一个条件变量,即创建了一个条件变量- 初始化条件变量:
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);:动态初始化条件变量- 参数:
- 条件变量的地址
- 条件变量的属性,一般传
nullptr
- 返回值:成功返回0,失败返回错误码(线程库中定义)
- 参数:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;:静态初始化条件变量
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);:将线程放入休眠队列,等待其他线程唤醒- 参数:
- 条件变量的地址
- 互斥锁地址,为解决多个消费者进程进入休眠队列时产生的竞态问题,需要使用互斥锁
- 返回值:成功返回0,失败返回错误码(线程库中定义)
- 参数:
- 唤醒消费者线程:
int pthread_cond_broadcast(pthread_cond_t *cond);:唤醒休眠队列中的所有消费者线程- 参数:条件变量的地址
- 返回值:成功返回0,失败返回错误码(线程库中定义)
int pthread_cond_signal(pthread_cond_t *cond);:唤醒休眠队列中的队首消费者线程- 参数:条件变量的地址
- 返回值:成功返回0,失败返回错误码(线程库中定义)
int pthread_cond_destroy(pthread_cond_t *cond);:销毁条件变量- 参数:条件变量地址
- 返回值:成功返回0,失败返回-1并置位错误码
#include <myHead.h>
using namespace std;
pthread_cond_t cond; //创建条件变量
pthread_mutex_t mutex; //创建互斥锁
//生产者任务
void* task1(void* arg) {
for (int i = 1;i <= 3;i++) {
sleep(1);
fputs("生产了一张5090\n", stdout);
pthread_cond_signal(&cond);
}
// sleep(1);
// fputs("生产了三张5090\n", stdout);
// pthread_cond_broadcast(&cond);
pthread_exit(nullptr);
}
//消费者任务
void* task2(void* arg) {
pthread_mutex_lock(&mutex); //上锁
pthread_cond_wait(&cond, &mutex); //进入休眠队列
fputs("买了一张5090\n", stdout);
pthread_mutex_unlock(&mutex); //解锁
pthread_exit(nullptr);
}
int main(int argc, char* argv[]) {
pthread_cond_init(&cond, nullptr); //初始化条件变量
pthread_mutex_init(&mutex, nullptr); //初始化互斥锁
pthread_t tid1, tid2, tid3, tid4;
if (pthread_create(&tid1, nullptr, task1, nullptr) != 0) {
fputs("tid1 creaete error\n", stdout);
return -1;
}
if (pthread_create(&tid2, nullptr, task2, nullptr) != 0) {
fputs("tid2 creaete error\n", stdout);
return -1;
}
if (pthread_create(&tid3, nullptr, task2, nullptr) != 0) {
fputs("tid2 creaete error\n", stdout);
return -1;
}
if (pthread_create(&tid4, nullptr, task2, nullptr) != 0) {
fputs("tid2 creaete error\n", stdout);
return -1;
}
pthread_join(tid1, nullptr);
pthread_join(tid2, nullptr);
pthread_join(tid3, nullptr);
pthread_join(tid4, nullptr);
pthread_cond_destroy(&cond); //销毁条件变量
pthread_mutex_destroy(&mutex); //销毁互斥锁
return 0;
}
wait原理
pthread_mutex_lock(&mutex); //外部上锁
while(资源==nullptr){
pthread_cond_wait(&cond,&mutex); //内部有一次解锁加锁操作
}
//C++11实现:
//cond.wait(lock,[](){return 资源!=nullptr});
//访问临界资源
pthread_mutex_unlock(&mutex); //外部解锁锁p
- 外部加锁
- 将线程放在等待队列后,内部解锁,以免其他线程无法访问临界资源
- 等待唤醒后阻塞竞争锁,竞争到锁后,内部加锁
- wait返回后条件不一定满足
- 访问临界资源
- 外部解锁
C++11中的多线程
支持线程创建,互斥锁,条件变量
头文件<thread>,封装了C风格的线程API,编译需要加-lpthread
线程相关操作
-
创建线程
- 通过构造函数创建一个线程类对象,构造函数参数为一个线程体函数,若线程体函数有参数,需要在传入的线程体函数后面再把参数传进去
- 传入的线程体函数不限返回值和参数类型,函数可以是任意类型:全局函数,类成员函数,仿函数,Lambda表达式
#include <myHead.h> using namespace std; class Myclass { public: void memberFunct(int num) { cout << "num=" << num << endl; } }; int main(int argc, char* argv[]) { Myclass a; thread th1(&Myclass::memberFunct, &a, 12); th1.join(); return 0; } -
this_thread::get_id():获取线程号 -
回收线程资源
th.join():阻塞回收,线程只能回收一次,使用joinable()函数判断是否可以回收th.detach():非阻塞回收
互斥锁
头文件
<mutex>
使用std::unique_lock,std::lock_guard更安全
成员函数:
- 构造函数创建互斥锁
lock():上锁unlock():解锁- 析构函数销毁互斥锁
条件变量
头文件
<condition_variable>
成员函数:
- 构造函数创建条件变量
wait(lock):将消费者线程放入休眠队列中- 参数:
- 用于保护条件变量的互斥锁
- 可以加一个参数
pred,谓词,相当于唤醒的附加条件
- 参数:
notify_one():唤醒一个进程notify_all():唤醒所有进程- 析构函数销毁条件变量
#include <myHead.h>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;
condition_variable con;
mutex mu;
void task1() {
for (int i = 1;i <= 5;i++) {
sleep(1);
cout << "生产一辆BYD" << endl;
con.notify_one();
}
}
void task2() {
unique_lock<mutex> lock(mu);
con.wait(lock);
cout << "买一辆BYD" << endl;
mu.unlock();
}
int main(int argc, char* argv[]) {
thread th1(task1);
for (int i = 1;i <= 5;i++) {
thread th2(task2);
th2.detach();
}
th1.join();
return 0;
}

浙公网安备 33010602011771号