• std::future的其他成员函数
  • std::shared_future
  • 原子操作、概念、基本用法

多线程主要是为了执行某个函数,本文的函数的例子,采用如下写法

int mythread()
{
cout << "my thread start, and thread id is " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(1000);
std::this_thread::sleep_for(dura);
cout << "my thread end, and thread id is " << std::this_thread::get_id() << endl; return 5;
} int mythread1(int aa)
{
cout << "my thread start, and thread id is " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(1000);
std::this_thread::sleep_for(dura);
cout << "my thread end, and thread id is " << std::this_thread::get_id() << endl; return 5;
} int mythread2(std::future<int> &tmp)
{
cout << "my thread start, and thread id is " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(1000);
std::this_thread::sleep_for(dura);
cout << "my thread end, and thread id is " << std::this_thread::get_id() << endl; return 5;
}

  

  

std::future的其他成员函数

成员函数wait_for有三个返回值

如果遇到以下情况

1)主线程等待子线程执行完毕,然后返回结果,线程有三种状态,那么就要用到std::future_status

enum class future_status { // names for timed wait function returns
ready,
timeout,
deferred
};

 在主函数中写

	cout << "Main thead " << std::this_thread::get_id() << endl;
std::future<int> result = std::async(std::launch::deferred, mythread);
//如果第一个参数使用 std::launch::deferred,线程会被延迟执行,到了get cout << "continue....!" << endl; std::future_status status = result.wait_for(std::chrono::milliseconds(6000)); if (status == std::future_status::timeout) {
cout << "线程执行超时,线程还未执行完" << endl;
//主线程想要等待子线程的结果,如果超时,状态就会变成超时 }
else if (status == std::future_status::ready) {
cout << "线程成功执行完毕,返回" << endl;
cout << "result:" << result.get() << endl;
}
else if (status == std::future_status::deferred)
{
//如果async的第一个参数被设置为延迟执行,std::launch::deferred, 则本条件成立
cout << "线程被延迟执行" << endl;
cout << "result:" << result.get() << endl;
//这个时候实际上没有创建一个新的子线程,函数在主线程中执行
}

  ready表示线程成功返回、timeout表示等待超时(线程没有成功返回)、deferrd表示延迟执行(调用get才执行)。注意async的第一参数是否为deferred。

std::shared_future

futured对象中的get只能获取一次,为什么第二次get会得到一个异常,这个异常主要就是因为get函数的设计,是一个移动语义,相当于把里面的对象移动到另外一个内存中,再次调用的话就会报异常。如果有多个线程都想要获得get的结果,此时就需要使用std::shared_future。

std::shared_future也是一个类模板,此时get函数就不是转移数据,而是复制数据。

get多次的情况

int mythread3(std::shared_future<int>& tmp)
{
cout << "my thread start, and thread id is " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(1000);
std::this_thread::sleep_for(dura);
cout << tmp.get() << endl;
cout << tmp.get() << endl;
cout << "my thread end, and thread id is " << std::this_thread::get_id() << endl; return 5;
}

  在main函数中

	cout << "Main thread start" << endl;
std::packaged_task<int(int)> mypt(mythread1);
std::thread t1(std::ref(mypt), 1);
t1.join();
std::future<int> result = mypt.get_future();
//std::shared_future<int> results(std::move(result)); //用std::move转成右值类型
std::shared_future<int> results(result.share());
std::thread t2(mythread3, std::ref(results));
t2.join();

  

原子操作、概念、基本用法

互斥量:用来在多线程编程中,保护共享数据:用一把锁把共享数据锁住,操作完毕之后再把锁打开。

一个线程读变量值,另外一个线程往变量中写值。

	//读线程
int tmpvalue = atomvalue;
//写线程
atomvalue = 0;

  读线程A和写线程B,如果写线程不断地往下写值,可能是读到新的值,也可能读到老的值,真正情况,会读到一个中间值,不可预料。即使一个简单的读或者幅值语句,也是分成很多步骤,一条语句会被拆成三四条汇编代码。

例子

int g_mycount = 0; //创建一个全局变量
void mythred_write() {
//线程入口函数
for (int i = 0; i < 1000000; i++) {
g_mycount++;
}
return;
}

 main函数中

	std::thread myobj1(mythred_write);
std::thread myobj2(mythred_write);
myobj1.join();
myobj2.join(); //两个线程执行完毕
cout << "加完的结果" << g_mycount << endl;

  程序的执行结果,此时程序的执行结果并不是想象中的1,000,000+1,000,000:

返回值和我们想像中的不符合,线程在操作的时候不稳定,代码被拆分为多条汇编语言执行,加法的代码没有成功执行完,就被打断。可以用互斥量的知识来解决问题。

std::mutex mymutex;

//线程入口函数修改
for (int i = 0; i < 1000000; i++) {
mymutex.lock();
g_mycount++;
mymutex.unlock();
}

  

除了用互斥量加锁的操作,用别的操作使得程序也达到同样的效果-->原子操作,无锁的多线程并发编程方式,或者也可以理解成原子操作是在多线程中不会被打断的程序执行片段。效率上而言,原子操作比互斥量效率上更胜一筹。有一点需要注意,互斥量,不仅仅加锁一行代码,原子操作一般针对的是一个变量,而不是一个代码段。在计算机中,原子操作是不可分割的操作,不可能出现中间状态。

std::atomic_int g_mycount = 0; //创建一个全局变量

  atomic 是用来封装某给类型的值,可以定义成一个原子的全局量。

std::atomic<int> g_mycount = 0; //创建一个全局变量

  像操作一个int对象一样来操作变量。根据范例,实用性为主,记住几个基本的用法范例即可。

心得体会:

1) std::atomic针对变量的赋值和判断,原子操作不能用于太复杂的操作。原子操作有用处,但是用处是有限的,在实际工作中,原子操作用的不太多,一般用于计数或者统计,累计发送出去了多少个数据包,接收了多少个数据包。实际工作中,如果有多个线程用来计数,可以考虑一下采用std::atomic变量。

2) 实际工作中,写商业代码,要谨慎行动,不太清楚这行代码有什么副作用,可以写一小段代码论证想法是否正确。或者干脆不使用。

C++并发与多线程学习笔记--future成员函数、shared_future、atomic的更多相关文章

  1. C++并发与多线程学习笔记--async、future、packaged_task、promise

    async future packaged_task promise async std:async 是个函数,用来启动一个异步任务,启动起来一个异步任务之后,返回一个std::futre对象,启动一 ...

  2. C++并发与多线程学习笔记--互斥量、用法、死锁概念

    互斥量(mutex)的基本概念 互斥量的用法 lock(), unlock() std::lock_guard类模板 死锁 死锁演示 死锁的一般解决方案 std::lock()函数模板 std::lo ...

  3. C++并发与多线程学习笔记--多线程数据共享问题

    创建和等待多个线程 数据和共享问题分析 只读的数据 有读有写 其他案例 共享数据的保护案例代码 创建和等待多个线程 服务端后台开发就需要多个线程执行不同的任务.不同的线程执行不同任务,并返回执行结果. ...

  4. C++并发与多线程学习笔记--参数传递详解

    传递临时对象 陷阱 总结 临时对象作为线程参数 线程id的概念 临时对象构造时的抓捕 成员函数指针做线程函数 传递临时对象作为线程参数 创建的工作线程不止一个,线程根据编号来确定工作内容.每个线程都需 ...

  5. C++并发与多线程学习笔记--unique_lock详解

    unique_lock 取代lock_quard unique_lock 的第二个参数 std::adopt_lock std::try_to_lock std::defer_lock unique_ ...

  6. C++并发与多线程学习笔记--基本概念和实现

    基本概念 并发 可执行程序.进程.线程 学习心得 并发的实现方法 多进程并发 多线程并发 总结 C++标准库 基本概念 (并发.进程.线程)区分C++初级编程和中高级编程 并发 两个或者更多的任务同时 ...

  7. C++并发与多线程学习笔记--atomic

    std::atomic std::async std::atomic 一般atomic原子操作,针对++,--,+=,^=是支持的,其他结果可能不支持. 注意 std::atomic<int&g ...

  8. C++并发与多线程学习笔记--单例设计模式、共享数据分析

    设计模式 共享数据分析 call_once 设计模式 开发程序中的一些特殊写法,这些写法和常规写法不一样,但是程序灵活,维护起来方便,别人接管起来,阅读代码的时候都会很痛苦.用设计模式理念写出来的代码 ...

  9. C++并发与多线程学习笔记--线程之间调度

    condition_variable wait() notify_one notify_all condition_variable 条件变量的实际用途: 比如有两个线程A和B,在线程A中等待一个条件 ...

随机推荐

  1. WebAssembly in Action

    WebAssembly in Action 数据加密,反爬虫,防盗链,版权保护,数据追踪,埋点 blogs 加密,js 禁用检测,权限控制 WebAssembly 防盗链 wasm online id ...

  2. Cookie 政策

    Cookie 政策 合规/隐私协议 https://www.synology.cn/zh-cn/company/legal/cookie_policy Cookie Cookie 政策 生效日期:20 ...

  3. Iterable object of JavaScript

    数组是可迭代的,所以数组可以用于for of,字符串也是可迭代的,所以字符串也可以用作for of,那么,对象呢? 试一试: var somebody = { start:0, end:100 } f ...

  4. Python数据结构与算法_删除排序数组中的重复项(06)

    给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度. 不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成. ...

  5. 必知必会之 Java

    必知必会之 Java 目录 不定期更新中-- 基础知识 数据计量单位 面向对象三大特性 基础数据类型 注释格式 访问修饰符 运算符 算数运算符 关系运算符 位运算符 逻辑运算符 赋值运算符 三目表达式 ...

  6. go的循环

    目录 go的循环 一.语法 二.语法简写 1.省略第一部分 2.省略第二部分 3.省略第三部分 4.全省略:死循环 5.终极写法,简洁变形 go的循环 Go中只有for循环,没有while循环.因为w ...

  7. tcp粘包情况分析

    1 什么是粘包现象 TCP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾.在tcp长连接时,发送端发到buffer里面,接收端也有个buffe ...

  8. 后端程序员之路 13、使用KNN进行数字识别

    尝试一些用KNN来做数字识别,测试数据来自:MNIST handwritten digit database, Yann LeCun, Corinna Cortes and Chris Burgesh ...

  9. C# 处理PPT水印(三)—— 在PPT中添加多行(平铺)文本水印效果

    在PPT幻灯片中,可通过添加形状的方式,来实现类似水印的效果,可添加单一文本水印效果,即幻灯片中只有一个文本水印:也可以添加多行(平铺)文本水印效果,即幻灯片中以一定方式平铺排列多个文本水印效果.本文 ...

  10. GDB调试:从入门到入土

    GDB是类Unix操作糸统下使用命令行调试的调试软件,全名GNU Debugger,在NOI系列竞赛使用的NOI Linux系统中起很大作用(如果不想用毒瘤Guide或直接输出)(XXX为文件名) 1 ...