
并发可以在生活中的任何地方看到,走路时说话,听歌时编写代码. 在计算机术语中,“并发”是指在单个系统中同时执行多个独立活动,而不是依次执行一个接一个. 对于单核CPU,一次只能处理一个任务,但不能完全执行一个任务,然后执行下一个任务,但是总是在任务之间切换,并且每个任务在完成后都会执行. 任务看起来好像任务是并行发生的. 尽管并非严格同时执行多个任务,但我们仍将其称为并发. 真正的并发是在多核CPU上,它实际上可以同时执行多个任务,称为硬件并发.
并发并非没有代价. 在单核CPU上同时执行两个任务需要上下文切换的时间成本. 如下图所示:
假设将A和B的两个任务划分为10个相等大小的块,则单核CPU交替执行两个任务,每次执行一个块,所花费的时间就是不完成A任务在进入B之前,该任务花费的时间是原来的两倍,但更多. 这是因为系统需要执行从一个任务到另一个任务的上下文切换,这需要时间(图中的灰色框). 上下文切换要求操作系统为当前正在运行的任务保存CPU状态和指令指针,找出要切换到的任务,并为要切换的任务重新加载处理器状态. 然后将新任务的指令和数据加载到缓存中.
该应用程序分为多个独立的单线程进程,这些进程可以同时运行. 该过程的内部实现原理相对复杂,因此在此不再赘述.
这些独立的进程可以通过常规的进程间通信机制进行通信,例如管道,信号,消息队列,共享内存,存储映射的I / O,信号量,套接字等.

缺点:
好处:
线程就像轻量级进程,但是进程中的所有线程共享相同的地址空间,并且线程之间的大多数数据可以共享. 线程之间的通信通常通过共享内存来实现.
好处:
缺点:
主要原因有两个: 任务拆分和性能提高.

编写软件时,最好将相关代码放在一起并分离不相关的代码,这可以使程序更易于理解和测试. 将程序划分为不同的任务,每个线程执行一个任务或多个任务,这可以简化整个程序的逻辑.
在两种情况下,并发都可以提高性能.
任务并行性(task parallelism): 将单个任务分为几个部分并并行运行以减少运行时间. 尽管听起来很简单,但这实际上是一个相当复杂的过程. 想象一下,如果每个部分之间有很多部分,则一个部分的执行需要使用另一任务的执行结果. 此时,它不能很好地并行完成. 数据并行性: 每个线程对不同的数据部分执行相同的操作.
C ++ 98标准中没有线程库多线程编程之一——问题提出,但是在C ++ 11中最终提供了多线程标准库,该库提供了用于管理线程,保护共享数据,线程间同步操作,原子的类. 操作等.
与多线程库相对应的头文件是#include
一个简单的串行程序如下:
#include <iostream>
#include <thread>
void function_1() {
std::cout << "I'm function_1()" << std::endl;
}
int main() {
function_1();
return 0;
}

这是典型的单线程单进程程序. 任何程序都是一个进程,而main()函数是主线程. 单线程按顺序执行.
实际上,将上述程序转换为多线程程序非常简单. 让function_1()函数在另一个线程中执行:
#include <iostream>
#include <thread>
void function_1() {
std::cout << "I'm function_1()" << std::endl;
}
int main() {
std::thread t1(function_1);
// do other things
t1.join();
return 0;
}
分析:
首先,构造一个std ::线程对象t1,并在构造过程中传递一个参数. 此参数是一个函数. 该函数是该线程的入口函数. 执行该函数后,将执行整个线程. 成功创建线程后,它将立即启动,并且没有类似于start的函数来显式启动线程. 线程开始运行后,您需要明确决定是等待它的连接(join)还是分离它以使其独立运行(分离). 注意: 您只需要在销毁std :: thread对象之前做出此决定即可. 在此示例中,对象t1是堆栈上的变量,并且在执行主函数后将被销毁,因此您需要在主函数结束之前做出决定. 在此示例中,选择了t1.join(). 主线程将阻塞,直到子线程完成. join()函数的另一任务是回收线程中使用的资源.
线程对象的生命周期与对象内部管理的线程不同. 如果线程快速执行,则内部线程可能已经结束,但是线程对象仍然存在,或者线程对象可能已被破坏. 内部线程仍在运行.
假设t1线程是一个非常慢的线程,主线程不想等待子线程的结束要结束整个任务,直接删除t1.join()是不够的,程序将被终止(破坏性的t1将调用std ::终止,并且程序将在没有活动异常的情况下打印终止调用).

相应地,我们可以调用t1.detach(),以便t1线程在后台运行,并将所有权和控制权转移到C ++运行时库,以确保可以使用与该线程关联的资源线程退出后. 已正确回收. 参考UNIX守护程序进程的概念,此分离的线程称为守护程序线程. 分离线程后,即使线程对象被破坏,线程仍然可以在后台运行,但是由于对象被破坏多线程编程之一——问题提出,主线程无法通过对象名称与此线程进行通信. 例如:
#include <iostream>
#include <thread>
void function_1() {
//延时500ms 为了保证test()运行结束之后才打印
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::cout << "I'm function_1()" << std::endl;
}
void test() {
std::thread t1(function_1);
t1.detach();
// t1.join();
std::cout << "test() finished" << std::endl;
}
int main() {
test();
//让主线程晚于子线程结束
std::this_thread::sleep_for(std::chrono::milliseconds(1000)); //延时1s
return 0;
}
// 使用 t1.detach()时
// test() finished
// I'm function_1()
// 使用 t1.join()时
// I'm function_1()
// test() finished
分析:
由于线程输入函数内部有500ms的延迟,因此在打印之前已执行了test()且t1已被销毁,但是它负责的线程仍然可以运行,这就是detach()的作用. 如果消除主函数中的1s延迟,则会发现没有任何输出,因为主线程执行得太快,整个程序已结束,并且后台线程由C ++运行时库回收. 如果将t1.detach()替换为t1.join(),则在t1线程的执行结束之前,测试功能将不会执行.
一旦线程分离,就无法再次加入. 如果必须调用,程序将崩溃,可以使用joinable()函数确定线程对象是否可以调用join().
void test() {
std::thread t1(function_1);
t1.detach();
if(t1.joinable())
t1.join();
assert(!t1.joinable());
}
链接到我的简书
C ++并发编程与C ++线程#1: 简介
本文来自电脑杂谈,转载请注明本文网址:
https://www.pc-fly.com/a/jisuanjixue/article-166378-1.html
考虑去演戏吧
期待
质检总局是怕人家说他们只拿钱不干活儿