【多线程编程学习笔记14】使用C++11进行多线程编程_除了 join() 成员函数外,thread 类还提供有很多实用的成员函数,表 1 给大家列出了(1)

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

申明:本学习笔记是在该教程的基础上结合自己的学习情况进行的总结,不是原创,想要看原版的请看C语言中文网的多线程编程(C语言+Linux),该网站有很多好的编程学习教程,尤其是关于C语言的。

前面章节中,我们借助操作系统提供的接口实现了 C 语言多线程程序的编写。C++ 11 标准中新引入了与多线程编程相关的多个头文件,包括 、、、<condition_variable> 和 。

当我们在 Linux 环境中编写 C++ 的多线程程序时,既可以借助 POSIX 标准提供的 <pthread.h> 实现,也可以借助 C++11 标准提供的头文件实现。本节,我们就给大家详细地讲解如何利用 C++11 标准编写多线程程序。

线程的创建和使用

C++11 标准中,<thread>头文件提供了 thread 类(位于 std 命令空间中),专门用来完成线程的创建和使用。

1) 创建线程

一个线程可以用 thread 类的对象来表示,thread类中重载了多种构造函数,最常用的有以下两个:

//1、Fn 表示线程要执行的函数,args 表示向 Fn 传递的多个参数,此构造函数支持泛型
template <class Fn, class... Args>
explicit thread (Fn&& fn, Args&&... args);
//2、移动构造函数
thread (thread&& x) noexcept;

注意,thread 类只提供了移动构造函数,未提供拷贝构造函数。这意味着,我们不能直接将一个事先定义好的 thread 对象赋值给另一个 thread 对象,但可以将临时的(匿名的)thread 对象赋值给另一个 thread 对象。有关移动构造函数,读者可阅读《C++11移动构造函数详解》一文做详细了解。

POSIX 标准中,线程所执行函数的参数和返回值都必须为 void* 类型。而 thread 类创建的线程可以执行任意的函数,即不对函数的参数和返回值做具体限定。

举个例子:

#include <iostream>
#include <thread>
using namespace std;
void threadFun1(int n) {
    cout << "---thread1 running\n";
    cout << "n=" << n << endl;
}
void threadFun2(const char * url) {
    cout << "---thread2 running\n";
    cout << "url=" << url << endl;
}
int main() {
    //调用第 1 种构造函数
    thread thread1(threadFun1,10);
    //调用移动构造函数
    thread thread2 = std::thread(threadFun2,"http://c.biancheng.net");
    //阻塞主线程,等待 thread1 线程执行完毕
    thread1.join();
    //阻塞主线程,等待 thread2 线程执行完毕
    thread2.join();
    return 0;
}

程序执行结果为(不唯一):

—thread1 running
n=10
—thread2 running
url=http://c.biancheng.net

程序中,我们分别调用两种构造函数创建了两个线程,它们分别执行 threadFun1() 和 threadFun2() 函数。我们在主线程(main() 函数)中调用了 thread 类提供的 join() 成员函数,以 thread1.join() 为例,它的功能是阻塞主线程,直至 thread1 线程执行完毕后,主线程才能继续执行。

2) 线程的使用

除了 join() 成员函数外,thread 类还提供有很多实用的成员函数,表 1 给大家列出了几个最常用的函数:

成员函数功 能
get_id()获取当前 thread 对象的线程 ID。
joinable()判断当前线程是否支持调用 join() 成员函数。
join()阻塞当前 thread 对象所在的线程,直至 thread 对象表示的线程执行完毕后,所在线程才能继续执行。
detach()将当前线程从调用该函数的线程中分离出去,它们彼此独立执行。
swap()交换两个线程的状态。

注意,每个thread 对象在调用析构函数销毁前,要么调用 join() 函数令主线程等待子线程执行完成,要么调用 detach() 函数将子线程和主线程分离,两者比选其一,否则程序可能存在以下两个问题:

  • 线程占用的资源将无法全部释放,造成内存泄漏;
  • 当主线程执行完成而子线程未执行完时,程序执行将引发异常。

举个例子:

#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;
void threadFun1(int n) {
    sleep(5);
    cout << "---thread1 running\n";
    cout << "n=" << n << endl;
}
void threadFun2(const char * url) {
    cout << "---thread2 running\n";
    cout << "url=" << url << endl;
}
int main() {
    //调用第 1 种构造函数
    thread thread1(threadFun1, 10);
    //输出 thread1 线程的 ID
    cout << "thread1 ID:" << thread1.get_id() << endl;
    //调用移动构造函数
    thread thread2 = std::thread(threadFun2, "http://c.biancheng.net");
    //输出 thread2 线程的 ID
    cout << "thread2 ID:" << thread2.get_id() << endl;
    //将 thread1 与主线程分离开,thread1 线程独立执行。
    thread1.detach();
    //判断 thread2 线程是否可以调用 join() 函数
    if (thread2.joinable()) {
        //阻塞主线程,直至 thread2 线程执行完毕。
        thread2.join();
    }
    cout << "main finished" << endl;
    return 0;
}

假设程序编写在 thread.cpp 文件中,执行过程如下:

[root@localhost ~]# g++ thread.cpp -o thread.exe -std=c++11 -lpthread
[root@localhost ~]# ./thread.exe
thread1 ID:140278776624896
thread2 ID:140278768232192
—thread2 running
url=http://c.biancheng.net
main finished

如果在 Windows 环境中运行,将程序中引入的 <unistd.h> 头文件改为 <Windows.h>,将第 6 行的 sleep(5); 语句改为 Sleep(5); 语句即可。

程序中创建了 2 个线程,通过调用 get_id() 成员函数分别获得了它们的线程 ID,其中 thread1 线程独立执行,thread2 线程先于主线程执行完成。通过执行结果可以看到,thread1 线程的执行结果并没有显示到屏幕上,这是因为 thread1 线程还未执行输出语句,主线程就已经执行结束(整个进程也执行结束),thread1 线程无法将执行结果输出到屏幕上。

<thread>头文件中不仅定义了 thread 类,还提供了一个名为 this_thread 的命名空间,此空间中包含一些功能实用的函数,如表 2 所示

函数功 能
get_id()获得当前线程的 ID。
yield()阻塞当前线程,直至条件成熟。
sleep_until()阻塞当前线程,直至某个时间点为止。
sleep_for()阻塞当前线程指定的时间(例如阻塞 5 秒)。

有关表 2 中这些函数的用法,我们不再一一举例,感兴趣的读者可查阅 C++ 函数手册。

实现线程同步

C++ 11 标准为解决“线程间抢夺公共资源”提供了多种方案,其中就包括我们前面讲到的互斥锁和条件变量。

1) 互斥锁

有关互斥锁实现线程同步的原理,这里不再赘述,您可以阅读《Linux互斥锁实现线程同步》一文做详细了解。

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

g-03FCGa8E-1715537278779)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值