C++多线程
创始人
2024-05-25 07:46:54
0

语言级别的多线程,优点是跨平台

底层仍然是调用系统API(识别不同系统,调用不同的系统调用)

创建线程

头文件thread

thread的构造函数:

thread() noexcept //构造线程对象,不执行任何任务
thread(thread&& _Other)noexcept//移动构造,将一个线程对象的资源所有权转移给新创建的线程对象,原来的线程对象不再代表线程
template
explicit thread(Function&& fx,Args&&... args)//创建线程对象,在线程中执行fx中的代码,args是传递给fx的参数,fx可以是普通函数,类的非静态、静态成员函数,lambda,仿函数
thread(const thread&) = delete;//不允许线程对象之间的拷贝//赋值函数:删除了参数为左值的赋值函数,如果参数为右值则转移资源所有权
thread& operator= (thread&& _Other)noexcept
thread& operator= (const thread&&) = delete;
// cppRL.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。#include 
#include
#include
#include
using namespace std;void func(int value,const string& str) {for (int i = 1; i <= 10; i++) {cout << "第" << i << "秒 " << str << value<

线程资源的回收

子线程资源不回收会产生僵尸线程

线程有异步性,线程的任务函数返回则线程终止,主线程退出则全部子线程终止(linux上),但是在语言级别会报错

//主线程先退出
#include 
#include
#include
#include
using namespace std;void func(int value,const string& str) {for (int i = 1; i <= 10; i++) {cout << "第" << i << "秒 " << str << value<

vs中的表示有些误导,在Linux下更直观

线程不共享的:栈、寄存器、状态、程序计数器

线程间共享的有:堆,全局变量,静态变量;

进程占有的资源有:地址空间,全局变量,打开的文件,子进程,信号量、账户信息

join()等待子线程退出(产生阻塞),回收资源

detach()分离子线程, 子线程退出,系统自动回收资源,分离后不可以join,主线程结束,整个进程结束,所有子线程自动结束

joinable()判断子线程的分离状态,返回bool

void func(int value,const string& str) {for (int i = 1; i <= 10; i++) {cout << "第" << i << "秒 " << str << value<

子线程如何结束:子线程函数运行完成则线程结束

当前线程

命名空间this_thread下的

//get_id();获取线程ID,thread类有同名函数void func(int value,const string& str) {cout << "t1 id:" << this_thread::get_id() << endl;for (int i = 1; i <= 10; i++) {cout << "第" << i << "秒 " << str << value<

mutex互斥锁

以下模拟三个窗口卖100张票程序,存在竞态条件,每次结果随着cpu对线程的不同调用顺序,可能不同

int cnt = 100;void sell(int i)
{while (cnt > 0){cout << "窗口:" << i << "卖出第:" << cnt << "张票" << endl;cnt--;this_thread::sleep_for(chrono::milliseconds(100));}
}
int main()
{listthreads;for (int i = 1; i <= 3; ++i){threads.push_back(thread(sell,i));}for (thread& t : threads){t.join();}cout << "所有窗口结束售卖" << endl;
}
//没有对临界资源互斥访问
void sell(int i)
{   while (cnt > 0){//仍然有问题,cnt==1时,多个线程可能在一段时间内都会进入while循环,mtx.lock();cout << "窗口:" << i << "卖出第:" << cnt << "张票" << endl;cnt--;mtx.unlock();this_thread::sleep_for(chrono::milliseconds(100));}
}
//改进:
while (cnt > 0){mtx.lock();if (cnt > 0){cout << "窗口:" << i << "卖出第:" << cnt << "张票" << endl;cnt--;}mtx.unlock();this_thread::sleep_for(chrono::milliseconds(100));}

lock_guard,unique_lock

如果在mtx.unlock前,异常,导致锁没有被释放:
使用lock_guard,封装互斥锁,且使用时加{}作用域,出作用域则自动进行析构,释放锁,类似scoped_ptr,不允许拷贝构造和赋值

void sell(int i)
{   while (cnt > 0){{   //加作用域lock_guardlock(mtx);if (cnt > 0){cout << "窗口:" << i << "卖出第:" << cnt << "张票" << endl;cnt--;}}this_thread::sleep_for(chrono::milliseconds(100));}
}

unique_lock类似unique_ptr,允许右值引用的拷贝构造和赋值

unique_locklock(mtx);if (cnt > 0){cout << "窗口:" << i << "卖出第:" << cnt << "张票" << endl;cnt--;}lock.unlock();

保证所有线程都能释放锁,防止死锁发生

lock_guard不可能用于函数参数传递或返回过程中,因为lock_guard不能拷贝构造和赋值,只能用于临界区代码段的简单互斥操作中

unique_lock可以用于函数调用过程中(右值引用)

atomic 原子类型

基于CAS(exchange,swap)操作(无锁操作)

对于类似全局变量的++,--操作,互斥锁较为繁琐

atomic_bool isReady = false;
atomic_int cnt = 0;void task()
{while (!isReady){this_thread::yield();}for (int i = 0; i < 100; ++i){cnt++;}
}
int main()
{listthreads;for (int i = 1; i <= 10; ++i){threads.push_back(thread(task));}this_thread::sleep_for(chrono::seconds(3));isReady = true;for (thread& t : threads){t.join();}cout << "count:" << cnt << endl;
}

多线程会对共享变量缓存,所以主线程改变isReady后,子线程不一定能立刻看到改后的值

所以要防止缓存:访问原始内存中的值

volatile atomic_bool isReady = false;
volatile atomic_int cnt = 0;

线程间的同步通信

生产者消费者模型

共享一个队列

C++ STL中的所有容器都不是线程安全的

#include 
#include
#include
#include
#include
#include
#include
using namespace std;mutex mtx;
condition_variable cv;//不为空则消费,为空则通知生产,为满则通知消费
//生产者生产一个,通知消费者消费一个,消费完,通知生产者生产
class Queue
{
public:void put(int val)   //生产{//生产者消费者同步unique_locklck(mtx);    //对临界区互斥while (!que.empty()){//que不为空,通知消费,生产者线程进入等待状态,且要释放互斥锁(使用条件变量)  cv.wait(lck);//进入等待状态则会将锁释放}que.push(val);cv.notify_all();//通知其他所有线程,消费,其他线程得到通知时,从等待->阻塞->获取到互斥锁才能继续执行cout << "生产者生产:" << val << endl;}int get()   //消费{unique_locklck(mtx);while (que.empty()){cv.wait(lck);//进入等待状态则会将锁释放}int val = que.front();que.pop();cv.notify_all();//通知生产者生产cout << "消费者消费:" << val << endl;return val;}private:queueque;
};void producer(Queue* que)
{for (int i = 0; i < 10; i++){que->put(i);this_thread::sleep_for(chrono::microseconds(100));}
}
void consumer(Queue* que)
{for (int i = 0; i < 10; i++){que->get();this_thread::sleep_for(chrono::microseconds(100));}
}
int main()
{Queue que;thread t1(producer,&que);thread t2(consumer,&que);t1.join();t2.join();}

条件变量wait使线程进入等待状态,且可以把持有的互斥锁先释放掉

notify_all()通知在条件变量上等待的线程,条件成立,解除等待

其他在条件变量上等待的线程,从等待->阻塞->获取到互斥锁才能继续执行

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
修复 爱普生 EPSON L4... L4151 L4153 L4156 L4158 L4163 L4165 L4166 L4168 L4...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...