C++11线程库 (三) 创建一个线程

一、线程对象如何获得?

线程对象的获取,可以分为以下几种情况:

  • case1 无中生有,通过传递函数对象+参数的方法完成一个线程的构造
  • case2 假借他人,通过默认构造函数并通过移动构造完成线程的创建

所以,我们标准库有以下的构造函数定义:

thread() noexcept;//since c++11
thread(thread&& other) noexcept;//since c++11
template<class Function,class ...Args>
explicit thread(Function &&f,Args && ...args);//since c++11
thread(const thread&)=delete;//since c++11
  • 第一个是默认构造,构造一个什么也不做的线程对象;
  • 第二个是std::move()会调用的移动构造函数;
  • 第三个是类模板,表示使用一个函数对象和若干个参数生成对应的线程类;

注意:线程不能拷贝,但是可以移动。移动之前应该保证移动对象是处于joinable状态,如果你默认构造一个线程,总是可以移动的,因为默认是joinable的

第三个构造函数是构造一个有实际还行意义的唯一方法,其模板参数如下:

  • 一个函数对象
  • 零个或者多个参数

参数很好理解,那么函数对象是什么,都有哪些种类呢?

二、函数对象

函数对象std::function是C++标准库定义的通用多态包装器,std::function 的实例能存储、复制及调用任何可复制构造 (CopyConstructible) 的可调用 (Callable) 目标:

  • 普通函数
  • lambda表达式
  • bind表达式
  • 其他可调用对象(如仿函数)
  • 指向成员函数指针
  • 成员数据是指向函数的指针

存储的可调用对象称为std::function对象的目标,如果目标不存在,称函数对象为空,调用它将会抛出std::bad_function_call错误。简单来说,函数对象类是存储函数对象的一个容器

上面列举的可调用目标都可以用统一的容器std::function封装。那么他都有哪些构造函数呢?

function() noexcept;                //(1)	(C++11 起)
function( std::nullptr_t ) noexcept;//(2)	(C++11 起)
function( const function& other );  //(3)	(C++11 起)
function( function&& other );		//(4)   (C++11 起,C20止)

第二个构造函数是最常用的,构造的参数是一个std::nullptr_t非空指针类型,也就是说:

  • lambada
  • 普通函数
  • 指向函数成员的指针
  • 数据成员是指针

都可以完成函数对象的构造!

注意:

  • 线程函数参数要么是拷贝,要么是移动。如果是引用参数,请使用std::ref或者std::cref包裹后传入。
  • 线程的返回值将会被忽略。如果一个函数抛出一个异常,将会调用std::terminate,如果你确实需要返回一个值或者异常给调用这个线程的线程,那么你可以使用std::promise或者std::async

三、例子

实例一:创建两个线程分别打印奇数和偶数
#include <iostream>
#include <thread>
using namespace std;

void show_even()
{
    for(int i=0;i<10;i+=2)
    {
        cout<<i;
    }   
};
void show_odd()
{
    for(int i=1;i<10;i+=2)
    {
        cout<<i;
    }
}
int main()
{
    thread th1(show_odd);
    thread th2(show_even);
    th1.join();
    th2.join();
    return 0;
}

每次运行的结果都不一样,任取三次结果:0246813579 1357902468 1350247968。cpu每次只会在一个线程中执行,如果处理的速度足够简单,可能一个线程所有内容完成了,另一个线程还没开始。

对于一个简单的执行内容,使用lambda创建一个可调用对象更为合适:

int main()
{
    thread th1([](){for(int i=0;i<10;i+=2){cout<<i;}});
    thread th2([](){for(int i=1;i<10;i+=2){cout<<i;}});
    th1.join();
    th2.join();
    return 0;
}

不用想函数名,不用专门写一个函数,blabla.

实例2:类的成员函数
class A
{
public:
    void printA(){std::cout<<"A"<<std::endl;}
    void showID(){std::cout<<"A's ID"<<std::endl;}
};


int main()
{
    A a;//首先需要有一个类的实例
    std::thread th1(&A::printA,&a);//第一个参数是类中地址,第二个参数是类实例地址
    a.showID();
    th1.join();
}

注意,上面这个例子只是printA函数中的语句泡在新的子线程中,showID因为调用时在主线程,所以其仍在主线程。

实例3:传递一个参数(引用和非引用)
class A
{
public:
    void printA(){std::cout<<"A"<<std::endl;}
    void doublePassedValue(int & value){value*=2;}
    void showID(){std::cout<<"A's ID"<<std::endl;}
    void addTwoPassedValue(int  value){value+=2;}

private:
    int m_id;
};
int main()
{
    int value=2;
    A a;//首先需要有一个类的实例
    std::thread th1(&A::doublePassedValue,&a,std::ref(value));//ok!用std::ref进行了包裹
    std::thread th1(&A::doublePassedValue,&a,value);//error
    std::thread th1(&A::addTwoPassedValue,&a,value);//ok!参数是值传递,不需要用std::ref
  
    th1.join();
}

20211022 更新了一些文字描述,增加了两个例子
20211130 增加了std::function的描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值