写点什么

c++ 的并发操作(多线程)

用户头像
赖猫
关注
发布于: 2021 年 05 月 14 日

C++11 标准在标准库中为多线程提供了组件,这意味着使用 C++编写与平台无关的多线程程序成为可能,而 C++程序的可移植性也得到了有力的保证。另外,并发编程可提高应用的性能,这对对性能锱铢必较的 C++程序员来说是值得关注的。

1. 何为并发

并发指的是两个或多个独立的活动在同一时段内发生。生活中并发的例子并不少,例如在跑步的时候你可能同时在听音乐;在看电脑显示器的同时你的手指在敲击键盘。这时我们称我们大脑并发地处理这些事件,只不过我们大脑的处理是有次重点的:有时候你会更关注你呼吸的频率,而有时候你更多地被美妙的音乐旋律所吸引。这时我们可以说大脑是一种并发设计的结构。这种次重点在计算机程序设计中,体现为某一个时刻只能处理一个操作。

与并发相近的另一个概念是并行。它们两者存在很大的差别。并行就是同时执行,计算机在同一时刻,在某个时间点上处理两个或以上的操作。判断一个程序是否并行执行,只需要看某个时刻上是否多两个或以上的工作单位在运行。一个程序如果是单线程的,那么它无法并行地运行。利用多线程与多进程可以使得计算机并行地处理程序(当然 ,前提是该计算机有多个处理核心)。


  • 并发:同一时间段内可以交替处理多个操作:


图中整个安检系统是一个并发设计的结构。两个安检队列队首的人竞争这一个安检窗口,两个队列可能约定交替着进行安检,也可能是大家同时竞争安检窗口(通信)。后一种方式可能引起冲突:因为无法同时进行两个安检操作。在逻辑上看来,这个安检窗口是同时处理这两个队列。


  • 并行:同一时刻内同时处理多个操作:


图中整个安检系统是一个并行的系统。在这里,每个队列都有自己的安检窗口,两个队列中间没有竞争关系,队列中的某个排队者只需等待队列前面的人安检完成,然后再轮到自己安检。在物理上,安检窗口同时处理这两个队列。

并发的程序设计,提供了一种方式让我们能够设计出一种方案将问题(非必须地)并行地解决。如果我们将程序的结构设计为可以并发执行的,那么在支持并行的机器上,我们可以将程序并行地执行。因此,并发重点指的是程序的设计结构,而并行指的是程序运行的状态。并发编程,是一种将一个程序分解成小片段独立执行的程序设计方法。

2.并发的基本方式途径

多线程与多进程是并发的两种途径。想象两个场景:

  • 场景一:你和小伙伴要开发一个项目,但小伙伴们放寒假都回家了,你们只能通过 QQ 聊天、手机通话、发送思维导图等方式来进行交流,总之你们无法很方便地进行沟通。好处是你们各自工作时可以互不打扰。

  • 场景二:你和小伙伴放假都呆在学校实验室中开发项目,你们可以聚在一起使用头脑风暴,可以使用白板进行观点的阐述,总之你们沟通变得更方便有效了。有点遗憾的是你在思考时可能有小伙伴过来问你问题,你受到了打扰。

这两个场景描绘了并发的两种基本途径。每个小伙伴代表一个线程,工作地点代表一个处理器。场景一中每个小伙伴是一个单线程的进程,他们拥有独立的处理器,多个进程同时执行;场景二中只有一个处理器,所有小伙伴都是属于同一进程的线程。

2.1 多进程并发

多个进程独立地运行,它们之间通过进程间常规的通信渠道传递讯息(信号,套接字,文件,管道等),这种进程间通信不是设置复杂就是速度慢,这是因为为了避免一个进程去修改另一个进程,操作系统在进程间提供了一定的保护措施,当然,这也使得编写安全的并发代码更容易。运行多个进程也需要固定的开销:进程的启动时间,进程管理的资源消耗。

2.2 多线程并发

在当个进程中运行多个线程也可以并发。线程就像轻量级的进程,每个线程相互独立运行,但它们共享地址空间,所有线程访问到的大部分数据如指针、对象引用或其他数据可以在线程之间进行传递,它们都可以访问全局变量。进程之间通常共享内存,但这种共享通常难以建立且难以管理,缺少线程间数据的保护。因此,在多线程编程中,我们必须确保每个线程锁访问到的数据是一致的。

3. C++中的并发与多线程

C++标准并没有提供对多进程并发的原生支持,所以 C++的多进程并发要靠其他 API——这需要依赖相关平台。C++11 标准提供了一个新的线程库,内容包括了管理线程、保护共享数据、线程间的同步操作、低级原子操作等各种类。标准极大地提高了程序的可移植性,以前的多线程依赖于具体的平台,而现在有了统一的接口进行实现。

C++11 新标准中引入了几个头文件来支持多线程编程:(所以我们可以不再使用 CreateThread 来创建线程,简简单单地使用 std::thread 即可。)

  • < thread > :包含 std::thread 类以及 std::this_thread 命名空间。管理线程的函数和类在 中声明.

  • < atomic > :包含 std::atomic 和 std::atomic_flag 类,以及一套 C 风格的原子类型和与 C 兼容的原子操作的函数。

  • < mutex > :包含了与互斥量相关的类以及其他类型和函数

  • < future > :包含两个 Provider 类(std::promise 和 std::package_task)和两个 Future 类(std::future 和 std::shared_future)以及相关的类型和函数。

  • < condition_variable > :包含与条件变量相关的类,包括 std::condition_variable 和 std::condition_variable_any。

3.1 初试多线程

   1、主进程等待子线程

#include <iostream>#include <thread>#include <Windows.h>  using namespace std;  void thread01(){    for (int i = 0; i < 5; i++)    {        cout << "Thread 01 is working !" << endl;        Sleep(100);    }}void thread02(){    for (int i = 0; i < 5; i++)    {        cout << "Thread 02 is working !" << endl;        Sleep(200);    }}  int main(){    thread task01(thread01);    thread task02(thread02);    task01.join();    task02.join();      for (int i = 0; i < 5; i++)    {        cout << "Main thread is working !" << endl;        Sleep(200);    }    system("pause");}
复制代码

2.主进程和子进程互不干扰

#include <iostream>#include <thread>#include <Windows.h>  using namespace std;  void thread01(){    for (int i = 0; i < 5; i++)    {        cout << "Thread 01 is working !" << endl;        Sleep(100);    }}void thread02(){    for (int i = 0; i < 5; i++)    {        cout << "Thread 02 is working !" << endl;        Sleep(200);    }}  int main(){    thread task01(thread01);    thread task02(thread02);    task01.detach();    task02.detach();      for (int i = 0; i < 5; i++)    {        cout << "Main thread is working !" << endl;        Sleep(200);    }    system("pause");}
复制代码

3.带参数的子线程

#include <iostream>#include <thread>#include <Windows.h>  using namespace std;  //定义带参数子线程void thread01(int num){    for (int i = 0; i < num; i++)    {        cout << "Thread 01 is working !" << endl;        Sleep(100);    }}void thread02(int num){    for (int i = 0; i < num; i++)    {        cout << "Thread 02 is working !" << endl;        Sleep(200);    }}  int main(){    thread task01(thread01, 5);  //带参数子线程    thread task02(thread02, 5);    task01.detach();    task02.detach();      for (int i = 0; i < 5; i++)    {        cout << "Main thread is working !" << endl;        Sleep(200);    }    system("pause");}
复制代码

4.多线程竞争的情况

有两个问题,一是有很多变量被重复输出了,而有的变量没有被输出;二是正常情况下每个线程输出的数据后应该紧跟一个换行符,但这里大部分却是另一个线程的输出。

这是由于第一个线程对变量操作的过程中,第二个线程也对同一个变量进行各操作,导致第一个线程处理完后的输出有可能是线程二操作的结果。针对这种数据竞争的情况,可以使用线程互斥对象 mutex 保持数据同步。

mutex 类的使用需要包含头文件 mutex:

#include <iostream>#include <thread>#include <Windows.h>#include <mutex>  using namespace std;  mutex mu;  //线程互斥对象  int totalNum = 100;  void thread01(){    while (totalNum > 0)    {        mu.lock(); //同步数据锁        cout << totalNum << endl;        totalNum--;        Sleep(100);        mu.unlock();  //解除锁定    }}void thread02(){    while (totalNum > 0)    {        mu.lock();        cout << totalNum << endl;        totalNum--;        Sleep(100);        mu.unlock();    }}  int main(){    thread task01(thread01);    thread task02(thread02);    task01.detach();    task02.detach();    system("pause");}
复制代码

3.2 在类中使用子线程的一个问题

  当我们再类中使用子线程我们会发现,我们不能把初始函数设置为类的成员函数,必须要把成员函数设置成 static 类型的才可以,但是这有设计到一个问题,就是 static 的类成员函数不能调用非 static 的变量成员,下面是一个两全其美的方法:

thread sendtask(bind(&client::sendata, this));//其中client是类的
复制代码


这样就可以解决我们的问题。

整理了一些最新 C/C++linux 服务器开发/架构师面试题、学习资料、教学视频和学习路线脑图(资料包括 C/C++,Linux,golang 技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg 等),免费分享有需要的可以自行添加学习交流群960994558


C/C++Linux 服务器开发/架构师入门学习地址:https://ke.qq.com/course/417774?flowToken=1031343


C++主要就是 Linux 平台下做开发,在各大招聘网站看一下就知道找工作自然不是问题,C++给人的感觉主要就是太难,还好我坚持下来了。C++虽然很难,但每年都有那么多 C++的软件工程师 ,肯定是有科学的方法只是你暂时没有找到。很多自学 C++的小伙伴儿一般都是困在学到什么程度可以找到工作,到底要学什么,怎么才能进入大厂等等各种问题。


用户头像

赖猫

关注

C/C++Linux服务器开发学习群960994558 2020.11.28 加入

纸上得来终觉浅,绝知此事要躬行

评论

发布
暂无评论
c++的并发操作(多线程)