C++多线程入门

std::async有三种启动选项:std::launch::async会创建一个线程,std::launch::deferred不创建线程,仅仅将函数的返回值包装为future,而std::launch::async | std::launch::deferred则自动选择,即根据目前的资源占用情况决定。这里先简单介绍一下线程的优势,线程和进程都是操作系统对资源调度的一种抽象,我们可以将线程视作一种轻量级的进程,一个进程中可以包含多个线程,而这些线程共享进程中的全部资源,比如堆栈、代码、全剧数据等等。
摘要由CSDN通过智能技术生成

我们先从一个最简单的例子入手:

#include <iostream>
#include <thread>
#include <chrono>

void sleep() {
    // sleep for 2 seconds
    std::this_thread::sleep_for(std::chrono::seconds(2));
}

int main() {
    auto startTime = std::chrono::high_resolution_clock::now();

    sleep(); // 2s
    sleep(); // 2s

    auto stopTime = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(stopTime - startTime);

    std::cout << "Duration: " << duration.count() / 1e6 << "s" << std::endl;
}

上述代码的运行时长应该是4s左右,这也符合串行编程的一贯逻辑,即执行完一段代码后再执行下一段代码。但是在许多应用中,串行编程会严重影响程序的执行效率,所以从c++11后引入的thread能让我们较快地上手并行编程,也就是多线程编程。

这里先简单介绍一下线程的优势,线程和进程都是操作系统对资源调度的一种抽象,我们可以将线程视作一种轻量级的进程,一个进程中可以包含多个线程,而这些线程共享进程中的全部资源,比如堆栈、代码、全剧数据等等。而线程仅有较少的私有资源,比如线程的调用栈等。因此,我们说线程是一个轻量级的进程。

我们对上面的代码进行修改使其可以多线程地调度:

// ...
int main() {
    auto startTime = std::chrono::high_resolution_clock::now();

    std::thread t1(sleep);
    std::thread t2(sleep);

    t1.join(); // 阻塞主线程,直至线程t1运行结束
    t2.join(); // 阻塞主线程,直至线程t2运行结束

    auto stopTime = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(stopTime - startTime);

    std::cout << "Duration: " << duration.count() / 1e6 << "s" << std::endl;
}

修改后,程序运行时间减少为2s。

1 创建一个线程

函数指针:

void countdown(int x) {
    while (x --> 0) std::cout << x << std::endl;
}

int main() {
    std::thread t(countdown, 10); // 使用函数指针创建线程
}
Lambda函数:int main() {
    std::thread t([] (int x) {
        while (x --> 0) std::cout << x << std::endl;
    }, 10); // 使用Lambda函数创建线程
}

函数对象(Functor,Function Object):

class Base {
public:
    void operator() (int x) {
        while (x --> 0) std::cout << x << std::endl;
    }
};

int main() {
    std::thread t(Base(), 10); // 使用函数对象指针创建线程
}

成员函数:

class Base {
public:
    void run(int x) {
        while (x --> 0) std::cout << x << std::endl;
    }

    static void static_run(int x) {
        while (x --> 0) std::cout << x << std::endl;
    }
};

int main() {
    Base b;
    std::thread t1(&Base::run, &b, 10); // 使用成员函数(非静态)指针创建线程,需要传入对象指针(&b)
    std::thread t2(&Base::static_run, 10); // 使用静态成员函数指针创建线程,不需要传入对象指针
}

2 Join和Detach

thread.join()的含义是等待(类似js中的await)该线程执行结束,但是如果执行两次join则会造成程序终止,因此可以使用thread.joinable()来检查该线程是否可调用join()。

thread.detach()的含义是将子线程从父线程中分离出去。当我们不使用任何thread时,main函数是在一个单进程中的单线程中,当我们创建一个线程时,main成为父线程,而新建的thread成为子线程。与join相同,detach也不能被调用两次,需要用joinable来进行检查。

对于一个thread对象,必须调用join或者detach,否则,当该线程析构时,会终止当前的进程。即当析构时,该thread对象的状态为joinable的话,调用std::terminate;。c++标准explicit处理的原因是不利于debug,具体原因这里不进行展开。

3 Mutex

Mutex是Mutual Exclusion(互斥)的意思,std::mutex的作用是处理所谓的竞争问题(Race Condition),即两个线程同时对一个关键变量(Critical Section,即只能有一个线程或进程同时进行访问的一个或一组变量或状态)进行读写的问题。我们假设有一个变量int x = 0;,如果连个线程T1和T2同时对变量x进行++操作,则有可能导致两个

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值