c++多线程并发学习笔记(0)
多进程并发:将应用程序分为多个独立的进程,它们在同一时刻运行。如图所示,独立的进程可以通过进程间常规的通信渠道传递讯息(信号、套接字、。文件、管道等等)。
优点:1.操作系统在进程间提供附附加的保护操作和更高级别的通信机制,意味着可以编写更安全的并发代码。
2. 可以使用远程连接的方式,在不同的机器上运行独立的进程,虽然增加了通信成本,但在设计精良的系统数上,这可能是一个提高并行可用性和性能的低成本方法。
缺点:1. 这种进程间的通信通常不是设置复杂,就是速度慢,这是因为操作系统会在进程间提供了一定的保护措施,以免一个进程去修改另一个进程的数据
2. 运行多个进程所需的固定开销:需要时间启动进程,操作系统需要内部资源来管理进程

多线程并发:在单个进程中运行多个线程。线程就是轻量级的进程:每个线程独立运行,且线程可以在不同的指令序列中运行。但是进程中的所有线程都共享地址空间,并且所有线程所访问的大部分数据---全局变量仍然是全局的,指针 对象的引用或者数据可以在线程之间传递。
优点:地址空间共此昂,以及缺少线程间数据的保护,使得操作系统的记录工作量减小,所以使用多线程的开销远远小于使用多进程
缺点:共享内存的灵活性的代价:如果数据要被多个线程访问,那么程序员必须宝成每个线程访问的数据是一致的,这意味着需要对线程间的通信做大量的工作

并发与并行:
并行更加注重性能。在讨论使用当前可用硬件来提高批量数据处理的速度时,我们会讨论程序的并行性;当关注的重点在于任务分离或任务响应时,就会讨论到程序的并发性
std::thread 学习
需要包含的头文件 <thread>
初始化线程(启动线程)
一个简单的例子:
#include <iostream>
#include <thread> using namespace std; void sayHello()
{
cout << "hello" <<endl;
} int main()
{
thread t(sayHello);
t.join();
}
初始化线程(启动线程)就是构造一个std::thread 的实例: std::thread(func)。 func 不简单的指函数,它是一个函数调用符类型,如下例子:
#include <iostream>
#include <thread> using namespace std; class Test
{
public:
void operator()()
{
cout << "hello" <<endl;
}
}; int main()
{
Test test;
thread t(test);
t.join();
}
也可以使用类的成员变量来初始化std::thread: std::thread(&Class::func, (Class)object)
#include <iostream>
#include <thread> using namespace std; class Test
{
public:
void sayHello()
{
cout << "hello" <<endl;
}
}; int main()
{
Test test;
thread t(&Test::sayHello, &test);
t.join();
}
注意:把函数对象传入到线程构造函数中时,需要避免以下情况: 如果你传递了一个临时变量,而不是一个命名的变量,c++的编译器会将其解释为函数声明,而不是类型对象的定义。例如:
std::thread myThread(func());
这里相当于声明了一个名为myThread的函数,这个函数带一个参数(函数指针指向一个没有参数并且返回func对象的函数),返回一个std::thread对象的函数,而不是启动了一个线程。
要解决这个问题,解决方法:
- 使用多组括号
std::thread myThread((func()));
- 使用大括号
std::thread myThread({func()});
- 使用lambda表达式
std::thread myThread([](){
do_something();
});
启动线程后,需要明确是要等待线程结束(加入式)还是让其自主运行(分离式),如果在对象销毁之前还没做出决定,程序就会终止(std::thread的析构函数会调用std::terminate())。即使有异常情况也要保证线程能够正确的加入(join)或者分离(detached)。
如果不等待线程,就要保证线程结束之前,可访问的数据的有效性。例如主线程往子线程中传了一个变量A的引用,子线程detach,则表示主线程可能在子线程之前结束,这样变量A便会被销毁,这时子线程再使用A的引用就会产生异常。处理这种情况的常规方法:使线程的功能齐全,将数据复制到线程中,而非复制到共享数据中。如果使用一个可调用的对象作为线程函数,这个对象就会复制到线程中,而后原始对象就会立即销毁,但对于对象中包含的指针和引用还需谨慎。最好不要使用一个访问局部变量的函数去创建线程。此外,可以通过join()函数来确保线程在函数完成前结束。
等待线程完成
使用std::thread 方法中的join()来实现等待线程完成
调用join()的行为,还清理了线程相关的存储部分,这样std::thread对象将不再与已经完成的线程有任何关联。这意味着,只能对一个线程使用一次join();一旦已经使用过join(),std::thread对象就不能再次加入了,当对其使用joinable()时,将返回false。
后台运行线程
使用std::thread 方法中的detach()来实现等待线程完成
使用detach()会让线程在后台运行,这就意味着主线程不能与之产生直接交互。也就是说,不会等待这个线程结束;如果线程分离,那么就不可能有std::thread对象能引用它,分离线程的确在后台运行,所以分离线程不能被加入。不过C++运行库保证,当线程退出时,相关资源的能够正确回收,后台线程的归属和控制C++运行库都会处理。当std::thread对象使用t.joinable()返回的是true,才可以使用t.detach()
向线程函数传递参数
在初始化的时候可以进行参数传递 std::thread myThread(func, arg0, arg1,...),另外在使用类的成员函数来初始化线程时的参数传递std::thread(&Class::func, (Class)object, arg0, arg1,...)
在这里需要注意两个问题:
1. 在将指向动态变量的指针作为参数传给线程的时候,想要依赖隐式转换将字面值转换为函数期待的对象(1),但是std::thread的构造函数会复制提供的变量,就只复制了没有转换成期望类型的字符串字面值。解决方法是:(2)在传入之前先显示的进行转换
void f(int i,std::string const& s);
void oops(int some_param)
{
char buffer[];
sprintf(buffer, "%i",some_param);
//std::thread t(f,3,buffer); //
std::thread t(f,,std::string(buffer)); // 2 使用std::string,避免悬垂指针
t.detach();
}
2. 期望传入一个引用,但整个对象被复制了。虽然期望传入一个引用类型的参数(1),但std::thread的构造函数并不知晓;构造函数无视函数期待的参数类型,并盲目的拷贝已提供的变量。
解决方法是使用std::ref()来将参数转换为引用的形式(2)
void update_data_for_widget(widget_id w,widget_data& data);
void oops_again(widget_id w)
{
widget_data data;
//std::thread t(update_data_for_widget,w,data); //
std::thread t(update_data_for_widget,w,std::ref(data)); // 2
display_status();
t.join();
process_widget_data(data);
}
转移线程所有权
首先要明确的是对于std::thread,不能将一个对象赋值给另一个对象,即赋值构造函数是被删除的。
thread(thread&) = delete;
但是我们有时需要转移线程的所有权,这时候就需要使用std::move()来实现
void some_function();
void some_other_function();
std::thread t1(some_function); //
std::thread t2=std::move(t1); // 2
t1=std::thread(some_other_function); // 3 隐式移动操作
std::thread t3; //
t3=std::move(t2); //
t1=std::move(t3); // 6 赋值操作将使程序崩溃
最后一个移动操作⑥,将some_function线程的所有权转移给t1。不过,t1已经有了一个关联的线程(执行some_other_function的线程),所以这里系统直接调用std::terminate()终止程序继续运行。这样做(不抛出异常,std::terminate()是noexcept函数)是为了保证与std::thread的析构函数的行为一致。需要在线程对象被析构前,显式的等待线程完成,或者分离它;进行赋值时也需要满足这些条件(说明:不能通过赋一个新值给std::thread对象的方式来"丢弃"一个线程)。
std::thread 支持移动操作,意味着它可以当做函数的返回值和参数
//作为函数返回值
std::thread g()
{
void some_other_function(int);
std::thread t(some_other_function,);
return t;
} //作为函数的参数
void f(std::thread t);
void g()
{
void some_function();
f(std::thread(some_function));
std::thread t(some_function);
f(std::move(t));
}
运行时决定线程数量
std::thread::hardware_concurrency() 这个函数会返回能并发在一个程序中的线程数量。例如,多核系统中,返回值可以是CPU核芯的数量。返回值也仅仅是一个提示,当系统信息无法获取时,函数也会返回0。
标识线程
线程标识类型为std::thread::id,获取方法有两种:
1. 调用std::thread对象的成员函数get_id()来直接获取,如果std::thread对象没有与任何执行线程相关联,get_id()将返回std::thread::type默认构造值,这个值表示“无线程”
2. 当前线程中调用std::this_thread::get_id()(这个函数定义在<thread>头文件中)也可以获得线程标识。
如果两个对象的std::thread::id相等,那它们就是同一个线程,或者都“无线程”。如果不等,那么就代表了两个不同线程,或者一个有线程,另一没有线程。
std::thread::id 有丰富的比较方法,因此它可以当做容器的键值,做排序等等比较。标准库也提供std::hash<std::thread::id>容器,所以std::thread::id也可以作为无序容器的键值。
参考资料:
https://chenxiaowei.gitbook.io/c-concurrency-in-action-second-edition-2019/
c++多线程并发学习笔记(0)的更多相关文章
- c++多线程并发学习笔记(1)
共享数据带来的问题:条件竞争 避免恶性条件竞争的方法: 1. 对数据结构采用某种保护机制,确保只有进行修改的线程才能看到修改时的中间状态.从其他访问线程的角度来看,修改不是已经完成了,就是还没开始. ...
- c++多线程并发学习笔记(2)
等待一个时间或其他条件 在一个线程等待完成任务时,会有很多选择: 1. 它可以持续的检查共享数据标志(用于做保护工作的互斥量),直到另一个线程完成工作时对这个标志进行重设.缺点:资源浪费,开销大 2. ...
- 多线程编程学习笔记——使用异步IO(一)
接上文 多线程编程学习笔记——使用并发集合(一) 接上文 多线程编程学习笔记——使用并发集合(二) 接上文 多线程编程学习笔记——使用并发集合(三) 假设以下场景,如果在客户端运行程序,最的事情之一是 ...
- 多线程编程学习笔记——使用异步IO
接上文 多线程编程学习笔记——使用并发集合(一) 接上文 多线程编程学习笔记——使用并发集合(二) 接上文 多线程编程学习笔记——使用并发集合(三) 假设以下场景,如果在客户端运行程序,最的事情之一是 ...
- Java多线程技术学习笔记(二)
目录: 线程间的通信示例 等待唤醒机制 等待唤醒机制的优化 线程间通信经典问题:多生产者多消费者问题 多生产多消费问题的解决 JDK1.5之后的新加锁方式 多生产多消费问题的新解决办法 sleep和w ...
- 多线程编程学习笔记——async和await(一)
接上文 多线程编程学习笔记——任务并行库(一) 接上文 多线程编程学习笔记——任务并行库(二) 接上文 多线程编程学习笔记——任务并行库(三) 接上文 多线程编程学习笔记——任务并行库(四) 通过前面 ...
- 多线程编程学习笔记——async和await(二)
接上文 多线程编程学习笔记——async和await(一) 三. 对连续的异步任务使用await操作符 本示例学习如何阅读有多个await方法方法时,程序的实际流程是怎么样的,理解await的异步 ...
- 多线程编程学习笔记——async和await(三)
接上文 多线程编程学习笔记——async和await(一) 接上文 多线程编程学习笔记——async和await(二) 五. 处理异步操作中的异常 本示例学习如何在异步函数中处理异常,学习如何对多 ...
- 多线程编程学习笔记——编写一个异步的HTTP服务器和客户端
接上文 多线程编程学习笔记——使用异步IO 二. 编写一个异步的HTTP服务器和客户端 本节展示了如何编写一个简单的异步HTTP服务器. 1.程序代码如下. using System; using ...
随机推荐
- shell练习--PAT题目1008:数组元素循环右移问题 (失败案例,运行超时)
一个数组A中存有N(>)个整数,在不允许使用另外数组的前提下,将每个整数循环向右移M(≥)个位置,即将A中的数据由(A0A1⋯AN−1)变换为(AN−M⋯AN−1A ...
- 【bzoj1059】[ZJOI2007]矩阵游戏
*题目描述: 小Q是一个非常聪明的孩子,除了国际象棋,他还很喜欢玩一个电脑益智游戏——矩阵游戏.矩阵游戏在一个N *N黑白方阵进行(如同国际象棋一般,只是颜色是随意的).每次可以对该矩阵进行两种操作: ...
- sh_08_打印小星星
sh_08_打印小星星 # 在控制台连续输出五行 *,每一行星号的数量依次递增 # * # ** # *** # **** # ***** # 1. 定义一个计数器变量,从数字1开始,循环会比较方便 ...
- 序列式容器————string
目录 前言 1.构造函数 2.size() 3.length() 4.maxsize() 5.capacity() 6.reserve() 7.resize() 8.获取元素at() 9.字符串比较c ...
- ArrayList遍历的三种方法
在输出很多的ArrayList的元素时,用普通的for循环太麻烦,因此本文介绍三种遍历ArrayList的方法 package test; public class Student { private ...
- Tree and Permutation
Tree and Permutation 给出一个1,2,3...N的排列,显然全部共有N!种排列,每种排列的数字代表树上的一个结点,设Pi是其中第i种排列的相邻数字表示的结点的距离之和,让我们求su ...
- 页面点击按钮下载excel(原生js)
let els = document.getElementsByTagName('iframe'); if(els.length > 0){ for(let i = 0;i < els.l ...
- cin.clear()、cin.sync()
看机器学习时,发现之前学的C++代码忘了,cin.clear().cin.sync() cin.clear():将流中的所有状态值都重设为有效值 cin.sync():清空流 这个很有意思,如果没有c ...
- wannafly 挑战赛9 E 组一组 (差分约束)
链接:https://www.nowcoder.com/acm/contest/71/E 时间限制:C/C++ 3秒,其他语言6秒 空间限制:C/C++ 65536K,其他语言131072K Spec ...
- 原型模式故事链(4)--JS执行上下文、变量提升、函数声明
上一章:JS的数据类型 传送门:https://segmentfault.com/a/11... 好!话不多少,我们就开始吧.对变量提升和函数声明的理解,能让你更清楚容易的理解,为什么你的程序报错了~ ...