C++ 多线程并发

Posted Angelia-Wang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ 多线程并发相关的知识,希望对你有一定的参考价值。

C++ 参考手册 - 并发支持库

《C++ Concurrency in Action》

https://segmentfault.com/a/1190000040628584?utm_source=sf-similar-article

https://zhuanlan.zhihu.com/p/547312117

bilibili C++ 多线程并发 基础入门教程

1 创建线程

C++11 之前原生不支持多线程,C++11起逐步引入了对线程的支持。

std::thread<thread> 头文件中声明,因此使用 std::thread 时需要包含 <thread> 头文件。

#include <iostream>
#include <thread>

void func(int a) 
    while (true) 
        std::cout << "hello world" << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(50));  // 休眠50毫秒
    

int main() 
    int n = 0;
    std::thread t1(func, n);
    return 0;

上述代码中我们创建了一个 thread 线程 t1, 让它调用函数 func,并为其传入函数参数 n。

❗️线程创建后即开始运行,不需要调用 run 之类的函数才能执行。

但我们发现程序运行没多久,就会报错,这是因为主线程 main 创建完子线程 t1 后没有任何代码,就退出了。而子线程 t1 还没有执行完毕,此时就会报错了。

解决方法:

1️⃣ 使用 join 函数

这样主线程 main 即使执行完成,也会一直等待 join 的子线程执行完毕,才会结束。

...
int main() 
    int n = 0;
    std::thread t1(func, n);
    t1.join();
    return 0;

2️⃣ 使用 detach 函数

该函数会将主线程和子线程完全分离开,二者不再有任何关系。主线程 main 创建完子线程 t1 后,执行后续代码,执行完毕就直接退出。

...
int main() 
    int n = 0;
    std::thread t1(func, n);
    t1.detach();
    return 0;

detach其实就是一个守护线程。

使用 detach() 时要注意访问数据的有效性,假如向子线程 t1 传入的参数是个指针/引用,在主线程 main 执行完毕退出后,指针指向的内容就会失效,而子线程中还在使用该指针,则会出现错误。

线程的入口函数 可以是:普通函数、类的静态/非静态成员函数、lambda 表达式。

1.1 其他操作

操作 功能 示例
swap(std::thread& other) 交换两个线程 std::thread t1(func, n);
std::thread t2(func, n);
t1.swap(t2);
get_id() 返回线程 id t1.get_id();
hardware_concurrency() 返回硬件所实现支持最大并发线程数
(值不一定准确,只能做参考)
t1.hardware_concurrency();
native_handle() 返回操作系统支持的线程句柄 t1.native_handle();

这些都是在创建子线程 t1 的主线程 main 中能操作的方法,若我要在子线程 t1 执行的函数 func 中获取这些数据,要如何调用?使用 std::this_thread 操作当前线程。

#include <iostream>
#include <thread>

void func(int a) 
    while (true) 
        std::cout << "thread_id = " << std::this_thread::get_id() << std::endl;
        std::cout << "hardware_concurrency = " << std::this_thread::hardware_concurrency() << std::endl;
        std::cout << "native_handle = " << std::this_thread::native_handle() << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(50));  // 休眠50毫秒
    

int main() 
    int n = 0;
    std::thread t1(func, n);
    return 0;

std::this_thread 还有其他的方法:

操作 功能 示例
sleep_for() 睡眠一段时间 std::this_thread::sleep_for (std::chrono::seconds(1));
sleep_until() 睡眠到一个绝对时间
yield() 当前线程放弃执行
操作系统调用另一线程继续执行
while (!ready) // wait until main() sets ready...
std::this_thread::yield();

2 互斥量(mutex)

2.1 基础使用

#include <iostream>
#include <thread>

int global_veriable = 0;
void task() 
    for (int i = 0; i < 1000; i++) 
        global_veriable++;
        global_veriable--;
    

int main() 
    std::thread t1(task);
    std::thread t2(task);
    t1.join();
    t2.join();
    std::cout << "current value is " << global_veriable;
    return 0;

看代码感觉 global_veritable 应该为 0,但是实际上可能每次运行都是不同的值。因为两个线程都会对该公共变量 global_veritable 进行读写访问。

多线程编程需考虑对公共资源的保护,否则涉及对公共资源访问的代码是不安全的。—— 互斥量(mutex)

std::mutex 对象提供了独占所有权的特性。在 <mutex> 头文件中声明,因此使用 std::mutex 时需要包含<mutex> 头文件。

现在,我们在

C++多线程编程

多线程是程序员必须掌握的一门技术,本文主要是针对于C++新标准中多线程库,需要具备一定C++基础方可学习。

前言


本章节是C++多线程编程第一课,C++不熟悉的可以转接C++专辑教程,本章节主要C++多线程编程中的一些基本概念以及几种创建线程的方式。

并发、进程、线程的基本概念


  • 并发

    两个或者多个任务(独立的活动)同时发生(进行):一个程序通知执行多个独立的任务

    并发假象(不是真正的并发):单核CPU通过上下文切换方式实现进程

  • 进程

    计算机中的程序关于某数据集合上的一次运行活动

    • 进程特性

    • 动态性:进程是程序的一次执行过程,是临时的,有生命期,是动态产生,动态消亡的;

    • 并发性:任何进程都可以同其他进行一起并发执行;

    • 独立性:进程是系统进行资源分配和调度的一个独立单位;

    • 结构性:进程由程序,数据和进程控制块三部分组成

  • 线程

    每个进程都有一个主线程并且主线程是唯一的,也就是一个进程只能有一个主线程。

    vs编译器中ctr+f5编译运行程序时,实际是主线程调用mian函数中的代码。

    线程可以理解为代码执行通道,除了主线程之外,可以自己创建其他线程。

  • 并发实现方案

    • 主要解决是进程间通信问题

    • 同一电脑上可通过管道,文件,消息队列,共享内存等方式实现

    • 不同电脑可通过socket网络通信实现

    • 多个进程实现并发

    • 单独进程,多个线程实现并发 即一个主线程,多个子线程实现并发

      一个进程中的所有线程共享内存空间(共享内存),例如全局变量,指针引用等,所以多线程开销远远小于多进程。共享内存也会导致数据一致性问题(资源竞争问题)。

C++线程编程基本操作


1.首先需要包含thread头文件

#include <thread>#include <iostream>

2.创建线程: thread类创建一个线程

#include <thread>void print(){ std::cout<<"子线程"<<endl; }int main(){ //运行程序会调用abort函数终止程序  std::thread t1(print);  std::cout<<"主线程"<<std::endl;}

3.join:加入/汇合线程。阻塞主线程,等待子线程执行结束,可理解为依附功能

#include <thread>void print(){ std::cout<<"子线程"<<endl; }int main(){ std::thread t1(print);  t1.join(); //阻塞主线程,等待子线程执行结束 std::cout<<"主线程"<<std::endl; return 0;}

4.detach:分离,剥离依附关系,驻留后台

#include <thread>#include <iostream>#include <windows.h>void print() { for (int i = 0; i < 10; i++)  { std::cout << "子线程"<<i << std::endl; }}int main() { std::thread t1(print); std::cout << "主线程" << std::endl; //可用Sleep延时实现结果演示 t1.detach(); return 0;}

注意:一旦detach线程后,便不可在使用join线程。

5.joinable:判断当前线程是否可以join或deatch,如果可以返回true,不能返回false

#include <thread>#include <iostream>void print() { for (int i = 0; i < 10; i++)  { std::cout << "子线程"<<i << std::endl; }}int main() { std::thread t1(print); t1.detach(); if (t1.joinable())  { t1.join(); std::cout << "可join" << std::endl; } std::cout << "主线程" << std::endl; return 0;}
其他创建线程方法


1.用类和对象

#include <thread>#include <iostream>class Function {public: void operator()() { std::cout << "子线程" << std::endl; }};int main() { Function object; std::thread t1(object); //可调用对象即可 t1.join(); std::thread t2((Function())); t2.join(); std::cout << "主线程" << std::endl; return 0;}

2.Lambda表达式

#include <thread>#include <iostream>int main() {
std::thread t1([] {std::cout << "子线程" << std::endl; }); t1.join(); std::cout << "主线程" << std::endl; return 0;}

3.带引用参数创建方式

#include <thread>#include <iostream>#include <thread>void printInfo(int& num) { num = 1001; std::cout << "子进程:"<<num << std::endl;}int main() { int num = 0; //std::ref 用于包装按引用传递的值。 //std::cref 用于包装按const引用传递的值 //error C2672: “invoke”: 未找到匹配的重载函数 std::thread t(printInfo, std::ref(num));  t.join(); std::cout << "主线程:"<<num << std::endl; return 0;}

4.带智能指针参数创建方式

#include <thread>#include <iostream>#include <thread>void printInfo(std::unique_ptr<int> ptr) { std::cout << "子线程:"<<ptr.get() << std::endl;}int main() { std::unique_ptr<int> ptr(new int(100)); std::cout << "主线程:" << ptr.get() << std::endl;  std::thread t(printInfo,std::move(ptr));  t.join(); std::cout << "主线程:"<<ptr.get() << std::endl; //主线程:00000000 move掉了 return 0;}

5.类的成员函数

#include <thread>#include <iostream>#include <thread>class MM {public: void print(int& num) { num = 1001; std::cout << "子线程:"<<num << std::endl; }};int main() { MM mm; int num = 10; std::thread t(&MM::print,mm,std::ref(num));  t.join(); std::cout << "主线程:"<< num << std::endl; return 0;}

好了,创建线程就介绍到这里,大家可以先练习一下,下章节讲解共享数据访问。喜欢的不如点个“在看”吧


以上是关于C++ 多线程并发的主要内容,如果未能解决你的问题,请参考以下文章

C++11 并发编程基础:并发并行与C++多线程

C++并发与多线程 13_线程池浅谈,线程数量总结

C++系列6:多线程

C++并发与多线程 4_创建多个线程数据共享问题分析

C++并发与多线程 10_shared_futureautomic

C++并发与多线程 2_线程启动结束,创建线程多种方法,join,detach