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 ...
随机推荐
- kafka消费者示范代码(Java)
1.将kafka里lib目录下(除jar包外还有别的东西)所有的jar包导入工程中. 2.代码 public static void main(String[] args) { //声明连接属性 Pr ...
- Nginx模块开发实验
工作原理: 当nginx接到 一个http请求之后,会找通过查找配置文件,并在配置文件中找到相应的地址映射,该地址也叫location block,而location中配置的文件会启动 相应的bloc ...
- linux-文件系统-5
cat /proc/partions cat /proc/mounts mount [options] -o [option] -t 文件类型 设备 挂载目录 设备: (1)设备文件:例如/dev/s ...
- win10 搜索框输入没提示
1.点击win, 手动在应用里找到Cortana(小娜) 2. 点右键->更多->应用设置,进入到下面的界面 3. 下拉到最下面,找到“重置”即可
- html applet标签 语法
html applet标签 语法 作用:定义嵌入的 applet. 说明:某些浏览器中依然存在对 <applet> 但是需要额外的插件和安装过程才能起作用.大理石机械构件 注释:HTML5 ...
- 关于win7系统下gitbook的安装与gitbook editor的配合使用
1.安装nodejs 2.node -v,可查看node版本: npm -v,可查看npm版本 3.npm install gitbook-cli -g,安装gitbook 此过程经常报错,如果报错, ...
- Linux基本命令使用(三)
1.压缩解压命令:gzip, .gz格式的 gzip 文件名 就压缩了. Linux压缩的放到Windows下可以解压,但是Windows下压缩到Linux解压就不一定可以. (1)只能压 ...
- HDU 2147--HDU 2147(博弈)--我可能做了假题。。。
kiki's game Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 40000/1000 K (Java/Others) Total Su ...
- java jdk原生的http请求工具类
package com.base; import java.io.IOException; import java.io.InputStream; import java.io.InputStream ...
- express中redirect传递数据
redirect中无法跟render一样传递数据 在index中,可以通过session重定向到login 在login.js 中获取req.session,渲染到login.ejs中,最后js获取