史上最全的C++面试宝典(九)—— 多线程

参考:https://www.runoob.com/cplusplus/cpp-tutorial.html

本教程旨在提取最精炼、实用的C++面试知识点,供读者快速学习及本人查阅复习所用。

目录

第九章  多线程

9.1  基本概念

9.2  C++线程管理

9.3  线程的同步与互斥

9.4  C++中的几种锁

9.5  C++中的原子操作

9.6  相关面试题


第九章  多线程

多线程是多任务处理的一种特殊形式,一般情况下,有基于进程和基于线程的两种类型的多任务处理方式。

  • 基于进程的多任务处理是程序的并发执行。
  • 基于线程的多任务处理是同一程序的片段的并发执行。

9.1  基本概念

9.1.1  进程与线程

进程是资源分配和调度的一个独立单位;而线程是进程的一个实体,是CPU调度和分配的基本单位。

同一个进程中的多个线程的内存资源是共享的,各线程都可以改变进程中的变量。因此在执行多线程运算的时候要注意执行顺序。

9.1.2  并行与并发

并行(parallellism)指的是多个任务在同一时刻同时在执行。

并发(concurrency)是指在一个时间段内,多个任务交替进行。虽然看起来像在同时执行,但其实是交替的。

9.2  C++线程管理

  • C++11的标准库中提供了多线程库,使用时需要#include <thread>头文件,该头文件主要包含了对线程的管理类std::thread以及其他管理线程相关的类。
  • 每个应用程序至少有一个进程,而每个进程至少有一个主线程,除了主线程外,在一个进程中还可以创建多个子线程。每个线程都需要一个入口函数,入口函数返回退出,该线程也会退出,主线程就是以main函数作为入口函数的线程。

9.2.1  启动线程

std::thread的构造函数需要的是可调用(callable)类型,除了函数外,还可以调用例如:lambda表达式、重载了()运算符的类的实例。

#include <iostream>
#include <thread>

using namespace std;

void output(int i)
{
    cout << i << endl;
}

int main()
{
    for (uint8_t i = 0; i < 4; i++)
    {
        //创建一个线程t,第一个参数为调用的函数,第二个参数为传递的参数
        thread t(output, i);
        //表示允许该线程在后台运行
        t.detach(); 
    }
    
    return 0;
}

在多线程并行的条件下,其输出结果不一定是顺序呢的输出1234,可能如下:

多线程并行
 

注意:

  • 把函数对象传入std::thread时,应传入函数名称(命名变量,如:output)而不加括号(临时变量,如:output())。
  • 当启动一个线程后,一定要在该线程thread销毁前,调用t.join()或者t.detach(),确定以何种方式等待线程执行结束:
    • detach方式,启动的线程自主在后台运行,当前的代码继续往下执行,不等待新线程结束。
    • join方式,等待关联的线程完成,才会继续执行join()后的代码。
    • 在以detach的方式执行线程时,要将线程访问的局部数据复制到线程的空间(使用按值传递),一定要确保线程没有使用局部变量的引用或者指针,除非你能肯定该线程会在局部作用域结束前执行结束。

9.2.2  向线程传递参数

向线程调用的函数只需要在构造thread的实例时,依次传入即可。

thread t(output, arg1, arg2, arg3, ...);

9.2.3  调用类成员函数

class foo
{
public:
    void bar1(int n)
    {
        cout<<"n = "<<n<<endl;
    }
    static void bar2(int n)
    {
        cout<<"static function is running"<<endl;
        cout<<"n = "<<n<<endl;
    }
};

int main()
{
    foo f;
    thread t1(&foo::bar1, &f, 5); //注意在调用非静态类成员函数时,需要加上实例变量。
    t1.join();
    
    thread t2(&foo::bar2, 4);
    t2.join();
}

9.2.4  转移线程的所有权

thread是可移动的(movable)的,但不可复制的(copyable)。可以通过move来改变线程的所有权,灵活的决定线程在什么时候join或者detach。

thread t1(f1);
thread t3(move(t1));

将线程从t1转移给t3,这时候t1就不再拥有线程的所有权,调用t1.join或t1.detach会出现异常,要使用t3来管理线程。这也就意味着thread可以作为函数的返回类型,或者作为参数传递给函数,能够更为方便的管理线程。

9.2.5  线程标识的获取

线程的标识类型为std::thread::id,有两种方式获得到线程的id:

  1. 通过thread的实例调用get_id()直接获取;
  2. 在当前线程上调用this_thread::get_id()获取。

9.2.6  线程暂停

如果让线程从外部暂停会引发很多并发问题,这也是为什么std::thread没有直接提供pause函数的原因。如果线程在运行过程中,确实需要停顿,就可以用this_thread::sleep_for。

void threadCaller()
{
    this_thread::sleep_for(chrono::seconds(3)); //此处线程停顿3秒。
    cout<<"thread pause for 3 seconds"<<endl;
}

int main()
{
    thread t(threadCaller);
    t.join();
}

9.2.7  异常情况下等待线程完成

为了避免主线程出现异常时将子线程终结,就要保证子线程在函数退出前完成,即在函数退出前调用join()。

方法一:异常捕获

void func() {
    thread t([]{
        cout << "hello C++ 11" << endl;
    });

    try
    {
        do_something_else();
    }
    catch (...)
    {
        t.join();
        throw;
    }
    t.join();
}

方法二:资源获取即初始化(RAII)

class thread_guard
{
    private:
        thread &t;
    public:
        /*加入explicit防止隐式转换,explicit仅可加在带一个参数的构造方法上,如:Demo test; test = 12.2;
        这样的调用就相当于把12.
  • 24
    点赞
  • 196
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
C++ 中,我们可以使用线程库来实现多线程编程。线程的挂起、唤醒与终止是多线程编程中非常重要的一部分。 线程的挂起也称为线程的休眠,它可以让线程停止运行一段时间,等待某个条件满足后再继续运行。在 C++ 中,我们可以使用 std::this_thread::sleep_for() 函数来实现线程的挂起,该函数可以让当前线程挂起一段时间,例如: ```cpp #include <chrono> #include <thread> int main() { // 挂起当前线程 1 秒钟 std::this_thread::sleep_for(std::chrono::seconds(1)); return 0; } ``` 线程的唤醒可以通过条件变量来实现,条件变量是一种同步机制,用于在线程之间传递信号。在 C++ 中,我们可以使用 std::condition_variable 类来创建条件变量,然后使用 wait() 函数来挂起线程等待条件变量的信号,使用 notify_one() 函数来唤醒一个等待条件变量的线程,例如: ```cpp #include <condition_variable> #include <mutex> #include <thread> std::condition_variable cv; std::mutex mtx; bool ready = false; void worker_thread() { // 等待条件变量的信号 std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, [](){ return ready; }); // 条件满足后继续执行 // ... } int main() { // 唤醒等待条件变量的线程 { std::lock_guard<std::mutex> lock(mtx); ready = true; } cv.notify_one(); return 0; } ``` 线程的终止可以使用 std::thread::join() 函数来实现,该函数可以让当前线程等待另一个线程执行完成后再继续执行,例如: ```cpp #include <thread> void worker_thread() { // ... } int main() { std::thread t(worker_thread); // 等待 worker_thread 执行完成 t.join(); return 0; } ``` 另外,线程的终止还可以使用 std::thread::detach() 函数来实现,该函数可以让当前线程与创建的线程分离,使得两个线程可以独立运行,例如: ```cpp #include <thread> void worker_thread() { // ... } int main() { std::thread t(worker_thread); // 分离线程,使得两个线程可以独立运行 t.detach(); return 0; } ``` 需要注意的是,分离线程后,主线程不能再使用 join() 函数等待子线程执行完成,否则会导致程序崩溃。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值