C++11多线程

线程:线程是操作系统能够进行CPU调度的最小单位,它被包含在进程之中,一个进程可包含单个或者多个线程。可以用多个线程去完成一个任务,也可以用多个进程去完成一个任务,它们的本质都相当于多个人去合伙完成一件事。

多线程并发:多线程是实现并发(双核的真正并行或者单核机器的任务切换都叫并发)的一种手段,多线程并发即多个线程同时执行,一般而言,多线程并发就是把一个任务拆分为多个子任务,然后交由不同线程处理不同子任务,使得这多个子任务同时执行。

C++多线程并发: (简单情况下)实现C++多线程并发程序的思路如下:将任务的不同功能交由多个函数分别实现,创建多个线程,每个线程执行一个函数,一个任务就这样同时分由不同线程执行了。

我们通常在何时使用并发? 程序使用并发的原因有两种,为了关注点分离(程序中不同的功能,使用不同的线程去执行),或者为了提高性能。当为了分离关注点而使用多线程时,设计线程的数量的依据,不再是依赖于CPU中的可用内核的数量,而是依据概念上的设计(依据功能的划分)。

知道何时不使用并发与知道何时使用它一样重要。 不使用并发的唯一原因就是收益(性能的增幅)比不上成本(代码开发的脑力成本、时间成本,代码维护相关的额外成本)。运行越多的线程,操作系统需要为每个线程分配独立的栈空间,需要越多的上下文切换,这会消耗很多操作系统资源,如果在线程上的任务完成得很快,那么实际执行任务的时间要比启动线程的时间小很多,所以在某些时候,增加一个额外的线程实际上会降低,而非提高应用程序的整体性能,此时收益就比不上成本。

创建线程

首先要引入头文件#include,管理线程的函数和类在该头文件中声明,其中包括std::thread类。

语句"std::thread th1(proc1);"创建了一个名为th1的线程,并且线程th1开始执行。

实例化std::thread类对象时,至少需要传递函数名作为参数。如果函数为有参函数,如"void proc2(int a,int b)",那么实例化std::thread类对象时,则需要传递更多参数,参数顺序依次为函数名、该函数的第一个参数、该函数的第二个参数,···,如"std::thread th2(proc2,a,b);"。

只要创建了线程对象(前提是,实例化std::thread对象时传递了“函数名/可调用对象”作为参数),线程就开始执行

当线程启动后,一定要在和线程相关联的std::thread对象销毁前,对线程运用join()或者detach()方法。

join()与detach()都是std::thread类的成员函数,是两种线程阻塞方法,两者的区别是是否等待子线程执行结束。

等待调用线程运行结束后当前线程再继续运行,例如,主函数中有一条语句th1.join(),那么执行到这里,主函数阻塞,直到线程th1运行结束,主函数再继续运行。

#include<iostream>
#include<thread>
using namespace std;
void proc(int &a)
{
    cout << "我是子线程,传入参数为" << a << endl;
    cout << "子线程中显示子线程id为" << this_thread::get_id()<< endl;
}
int main()
{
    cout << "我是主线程" << endl;
    int a = 9;
    thread th2(proc,a);//第一个参数为函数名,第二个参数为该函数的第一个参数,如果该函数接收多个参数就依次写在后面。此时线程开始执行。
    cout << "主线程中显示子线程id为" << th2.get_id() << endl;
    th2.join()//此时主线程被阻塞直至子线程执行结束。
    return 0;
}

调用join()会清理线程相关的存储部分,这代表了join()只能调用一次。使用joinable()来判断join()可否调用。同样,detach()也只能调用一次,一旦detach()后就无法join()了,有趣的是,detach()可否调用也是使用joinable()来判断。

如果使用detach(),就必须保证线程结束之前可访问数据的有效性,使用指针和引用需要格外谨慎

移动属性
std::thread对象的所有权只能传递不能复制。实际上,就std::thread对象,只具有移动属性,不具有复制属性。std::thread的构造函数如下:

class thread {
  private:
    id              _M_id;
  public:
    thread() noexcept = default;

    template<typename _Callable, 
              typename... _Args,
              typename = _Require<__not_same<_Callable>>>
    explicit thread(_Callable&& __f, _Args&&... __args) {
          //...
    }

    ~thread() {
      if (joinable())
        std::terminate();
    }
    // 禁止复制
    thread(const thread&) = delete;
    thread& operator=(const thread&) = delete;

    // std::thread 只具有移动属性
    thread(thread&& __t) noexcept
    { swap(__t); }

    thread& operator=(thread&& __t) noexcept {
      if (joinable())
          std::terminate();
      swap(__t);
      return *this;
    }
    //...
  }

可以发现,std::thread禁止了复制构造函数、复制赋值表达式,只留下了移动构造函数、赋值,使得std::thread对象只能移动,不能复制。这就是本文开篇demo中使用emplace_back函数添加std::thread对象的原因,防止触发复制构造函数。

向threadList中添加std::thread对象,有如下三种方式:

  threadList.emplace_back(std::thread{do_some_work, idx});  // 1) ok 

  std::thread trd{do_some_work, idx};
  threadList.push_back(trd);               // 2) error
  threadList.push_back(std::move(td));     // 3) ok
  threadList.emplace_back(std::move(td));  // 4) ok
获取硬件支持的并发线程数
std::thread::hardware_concurrency()
std::this_thread

std::this_thread是一个命名空间,包含一系列访问当前线程的函数

get_id(获取线程id)

std::this_thread::get_id();

yield
当前线程放弃执行,给实现重新调度的机会,允许其他线程运行,该线程回到准备状态,重新分配资源。
调用该方法后,可能执行其他线程,也可能还是执行该线程。

std::this_thread::yield();

sleep_for

阻塞线程到指定时间段之后

int main()
{
	this_thread::sleep_for(chrono::nanoseconds(1000));//阻塞当前线程1000纳秒
	this_thread::sleep_for(chrono::microseconds(1000));//阻塞当前线程1000微妙
	this_thread::sleep_for(chrono::milliseconds(1000));//阻塞当前线程1000毫秒
	this_thread::sleep_for(chrono::seconds(20)+ chrono::minutes(1));//阻塞当前线程1分钟20秒
	this_thread::sleep_for(chrono::hours(1));//阻塞当前线程1小时
	system("pause");
}

sleep_until

阻塞线程到指定时间点

int main()
{
	chrono::system_clock::time_point until = chrono::system_clock::now();
	until += chrono::seconds(5);
	this_thread::sleep_until(until);//阻塞到5秒之后
	system("pause");
}

std::async()

std::async函数原型

template<class Fn, class... Args>
future<typename result_of<Fn(Args...)>::type> async(launch policy, Fn&& fn, Args&&...args);

功能:第二个参数接收一个可调用对象(仿函数、lambda表达式、类成员函数、普通函数…)作为参数,并且异步或是同步执行他们。

对于是异步执行还是同步执行,由第一个参数的执行策略决定

(1)、std::launch::async 传递的可调用对象异步执行;

(2)、std::launch::deferred 传递的可调用对象同步执行;

(3)、std::launch::async | std::launch::deferred 可以异步或是同步,取决于操作系统,我们无法控制;

(4)、如果我们不指定策略,则相当于(3)。

对于执行结果

我们可以使用get、wait、wait_for、wait_until等待执行结束,区别是get可以获得执行的结果。如果选择异步执行策略,调用get时,如果异步执行没有结束,get会阻塞当前调用线程,直到异步执行结束并获得结果,如果异步执行已经结束,不等待获取执行结果;如果选择同步执行策略,只有当调用get函数时,同步调用才真正执行,这也被称为函数调用被延迟。

返回结果std::future的状态:

(1)、deffered:异步操作还没有开始;

(2)、ready:异步操作已经完成;

(3)、timeout:异步操作超时。

实例1(异步执行和同步执行):

// STLasync.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
  
#include "pch.h"
#include <iostream>
#include <string>
#include <chrono>
#include <thread>
#include <future>
  
using namespace std::chrono;
  
std::string fetchDataFromDB(std::string recvData) {
  
    std::cout << "fetchDataFromDB start" << std::this_thread::get_id() << std::endl;
    std::this_thread::sleep_for(seconds(5));
    return "DB_" + recvData;
}
  
std::string fetchDataFromFile(std::string recvData) {
  
    std::cout << "fetchDataFromFile start" << std::this_thread::get_id() << std::endl;
    std::this_thread::sleep_for(seconds(3));
    return "File_" + recvData;
}
  
int main() {
  
    std::cout << "main start" << std::this_thread::get_id() << std::endl;
  
    //获取开始时间
    system_clock::time_point start = system_clock::now();
  
    std::future<std::string> resultFromDB = std::async(std::launch::async, fetchDataFromDB, "Data");
  
    //从文件获取数据
    std::future<std::string> fileData = std::async(std::launch::deferred, fetchDataFromFile, "Data");
  
    //知道调用get函数fetchDataFromFile才开始执行
    std::string FileData = fileData.get();
    //如果fetchDataFromDB()执行没有完成,get会一直阻塞当前线程
    std::string dbData = resultFromDB.get();
     
    //获取结束时间
    auto end = system_clock::now();
  
    auto diff = duration_cast<std::chrono::seconds>(end - start).count();
    std::cout << "Total Time taken= " << diff << "Seconds" << std::endl;
  
    //组装数据
    std::string data = dbData + " :: " + FileData;
  
    //输出组装的数据
    std::cout << "Data = " << data << std::endl;
  
    return 0;
}

实例2(查询future的状态获取异步执行的结果):

// STLasync.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
  
#include "pch.h"
#include <iostream>
#include <string>
#include <chrono>
#include <thread>
#include <future>
  
using namespace std::chrono;
  
std::string fetchDataFromDB(std::string recvData) {
  
    std::cout << "fetchDataFromDB start" << std::this_thread::get_id() << std::endl;
    std::this_thread::sleep_for(seconds(5));
    return "DB_" + recvData;
}
  
  
int main() {
  
    std::cout << "main start" << std::this_thread::get_id() << std::endl;
  
    //获取开始时间
    system_clock::time_point start = system_clock::now();
  
    std::future<std::string> resultFromDB = std::async(std::launch::async, fetchDataFromDB, "Data");
  
    std::future_status status;
    std::string dbData;
    do
    {
        status = resultFromDB.wait_for(std::chrono::seconds(1));
  
        switch (status)
        {
        case std::future_status::ready:
            std::cout << "Ready..." << std::endl;
            //获取结果
            dbData = resultFromDB.get();
            std::cout << dbData << std::endl;
            break;
        case std::future_status::timeout:
            std::cout << "timeout..." << std::endl;
            break;
        case std::future_status::deferred:
            std::cout << "deferred..." << std::endl;
            break;
        default:
            break;
        }
  
    } while (status != std::future_status::ready);
  
     
    //获取结束时间
    auto end = system_clock::now();
  
    auto diff = duration_cast<std::chrono::seconds>(end - start).count();
    std::cout << "Total Time taken= " << diff << "Seconds" << std::endl;
  
    return 0;
}
std::promise

std::promise 对象可以保存某一类型 T 的值,该值可被 future 对象读取(可能在另外一个线程中),因此 std::promise 提供了一种线程同步的手段。在 std::promise 对象构造时可以和一个共享状态(通常是std::future)相关联,并可以在相关联的共享状态(std::future)上保存一个类型为 T 的值。

可以通过 get_future 来获取与该 std::promise 对象相关联的 std::future 对象,调用该函数之后,两个对象共享相同的共享状态。

std::promise 对象是异步 Provider,它可以在某一时刻设置共享状态的值。
std::future 对象可以异步返回共享状态的值,或者在必要的情况下阻塞调用者并等待共享状态标志变为 ready,然后才能获取共享状态的值。
下面以一个简单的例子来说明上述关系:

#include <iostream>       
#include <functional>     
#include <thread>        
#include <future>     // std::promise, std::future

void print_int(std::future<int>& fut) {
    int x = fut.get();                    // 获取共享状态的值.
    std::cout << "value: " << x << '\n';  // 打印 value: 10.
}

int main ()
{
    std::promise<int> prom;                    // 生成一个 std::promise<int> 对象.
    std::future<int> fut = prom.get_future();  // 和 future 关联.
    std::thread t(print_int, std::ref(fut));   // 将 future 交给另外一个线程t.
    prom.set_value(10);                        // 设置共享状态的值, 此处和线程t保持同步.
    t.join();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值