多进程并发:将应用程序分为多个独立的进程,它们在同一时刻运行。如图所示,独立的进程可以通过进程间常规的通信渠道传递讯息(信号、套接字、。文件、管道等等)。

优点: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)的更多相关文章

  1. c++多线程并发学习笔记(1)

    共享数据带来的问题:条件竞争 避免恶性条件竞争的方法: 1. 对数据结构采用某种保护机制,确保只有进行修改的线程才能看到修改时的中间状态.从其他访问线程的角度来看,修改不是已经完成了,就是还没开始. ...

  2. c++多线程并发学习笔记(2)

    等待一个时间或其他条件 在一个线程等待完成任务时,会有很多选择: 1. 它可以持续的检查共享数据标志(用于做保护工作的互斥量),直到另一个线程完成工作时对这个标志进行重设.缺点:资源浪费,开销大 2. ...

  3. 多线程编程学习笔记——使用异步IO(一)

    接上文 多线程编程学习笔记——使用并发集合(一) 接上文 多线程编程学习笔记——使用并发集合(二) 接上文 多线程编程学习笔记——使用并发集合(三) 假设以下场景,如果在客户端运行程序,最的事情之一是 ...

  4. 多线程编程学习笔记——使用异步IO

    接上文 多线程编程学习笔记——使用并发集合(一) 接上文 多线程编程学习笔记——使用并发集合(二) 接上文 多线程编程学习笔记——使用并发集合(三) 假设以下场景,如果在客户端运行程序,最的事情之一是 ...

  5. Java多线程技术学习笔记(二)

    目录: 线程间的通信示例 等待唤醒机制 等待唤醒机制的优化 线程间通信经典问题:多生产者多消费者问题 多生产多消费问题的解决 JDK1.5之后的新加锁方式 多生产多消费问题的新解决办法 sleep和w ...

  6. 多线程编程学习笔记——async和await(一)

    接上文 多线程编程学习笔记——任务并行库(一) 接上文 多线程编程学习笔记——任务并行库(二) 接上文 多线程编程学习笔记——任务并行库(三) 接上文 多线程编程学习笔记——任务并行库(四) 通过前面 ...

  7. 多线程编程学习笔记——async和await(二)

    接上文 多线程编程学习笔记——async和await(一) 三.   对连续的异步任务使用await操作符 本示例学习如何阅读有多个await方法方法时,程序的实际流程是怎么样的,理解await的异步 ...

  8. 多线程编程学习笔记——async和await(三)

    接上文 多线程编程学习笔记——async和await(一) 接上文 多线程编程学习笔记——async和await(二) 五.   处理异步操作中的异常 本示例学习如何在异步函数中处理异常,学习如何对多 ...

  9. 多线程编程学习笔记——编写一个异步的HTTP服务器和客户端

    接上文 多线程编程学习笔记——使用异步IO 二.   编写一个异步的HTTP服务器和客户端 本节展示了如何编写一个简单的异步HTTP服务器. 1.程序代码如下. using System; using ...

随机推荐

  1. Ubuntu18.04下更改apt源为阿里云源

    1.复制源文件备份,以防万一 我们要修改的文件是sources.list,它在目录/etc/apt/下,sources.list是包管理工具apt所用的记录软件包仓库位置的配置文件,同样类型的还有位于 ...

  2. 实战build-react(二)-------引入Ant Design

    安装 Ant Design  npm install antd --save 或 yarn add antd 注释:https://www.jianshu.com/p/21caf40ee93e(cop ...

  3. #420 Div2 Problem B Okabe and Banana Trees (math && 暴力枚举)

    题目链接 :http://codeforces.com/contest/821/problem/B 题意 :给出 m 和 b 表示在坐标轴上的一条直线  要求你在这条直线和x.y轴围成的区域中找出一个 ...

  4. sh_09_print函数的结尾

    sh_09_print函数的结尾 # 在默认情况下,print 函数输出内容之后,会自动在内容末尾增加换行 print("*", end="---") prin ...

  5. selenium,控制滚动条

    今天写selenium用例的时候,遇见奇葩的问题,FF下是没有错误的,但是在chrome和ie下就会有问题,后来发现是 操作中点击一个按钮,在页面不可见,就会导致异常,解决方法如下: element ...

  6. wordcloud:让你的词语像云朵一样美

    介绍   对文本中出现频率较高的关键词给予视觉化的显示 使用 python import jieba import codecs import wordcloud file = r"C:\U ...

  7. 6.并发编程--volatile

    并发编程--volatile volatile-说明 volatile关键字的作用是变量在多个线程可见: volatile 关键字是非原子性的 要是实现原子性操作,建议使用atomic类的系列对象:支 ...

  8. nested exception is java.lang.OutOfMemoryError: PermGen space

    原因: 持久带内存溢出. 方法:在启动的catalina.sh 里加上这个配置,增加持久带的大小. JAVA_OPTS="XX:PermSize=64M-XX:MaxPermSize=128 ...

  9. Python的复制,浅拷贝和深拷贝

    https://www.cnblogs.com/xueli/p/4952063.html 如果给一个变量赋值一个对象,那么新变量和原对象变量将会是同一个引用,其中一方改变,另一方也会改变. 该问题可以 ...

  10. gsensor架构和原理分析【转】

    本文转载自:http://blog.csdn.net/u012296694/article/details/48055491 本文主要描述了在android2.3平台G-sensor相关软硬件的体系架 ...