C++多线程编程(thread类)

多线程库

  C++11中提供了多线程的标准库,提供了管理线程、保护共享数据、线程间同步操作、原子操作等类。

  多线程库对应的头文件是#include <thread>,类名为std::thread

串行程序:

#include <iostream>
#include <thread>

void function_1() 
{
    std::cout << "I'm function_1()" << std::endl;
}

int main() 
{
    function_1();
    return 0;
}

  这是一个典型的单线程的单进程程序,任何程序都是一个进程,main()函数就是其中的主线程,单个线程都是顺序执行。

  将上面的程序改造成多线程程序,让function_1()函数在另外的线程中执行:

#include <iostream>
#include <thread>

void function_1() 
{
    std::cout << "I'm function_1()" << std::endl;
}

int main() 
{
    std::thread t1(function_1);
    // do other things
    t1.join();
    return 0;
}

  1. 构建一个std::thread类的对象t1,构造的时候传递了一个参数,这个参数是一个函数,这个函数就是这个线程的入口函数,函数执行完整个线程也就执行完了;

  2. 线程创建成功后,就会立即启动;

  3. 一旦线程开始运行, 就需要显式的决定是要等待它完成(join),或者分离它让它自行运行(detach)。需要在std::thread对象被销毁之前做出决定;

  本例选择了使用t1.join(),主线程(main函数)会一直阻塞,直到子线程(function_1函数)完成,join()函数的另一个任务是回收该线程中使用的资源

  我们也可以调用t1.detach(),从而将子线程放在后台运行,所有权和控制权被转交给C++运行时库,以确保与线程相关联的资源在线程退出后能被正确的回收。被分离的线程被称为守护线程(daemon threads)。线程被分离之后,即使该线程对象被析构了,线程还是能够在后台运行,只是由于对象被析构了,主线程不能够通过对象名与这个线程进行通信。线程对象(t1)和对象内部管理的线程(子线程)的生命周期并不一样;如果线程执行的快,可能内部的线程已经结束了,但是线程对象还活着;也有可能线程对象已经被析构了,内部的线程还在运行。

#include <iostream>
#include <thread>

void function_1() 
{
    //延时500ms 为了保证test()运行结束之后才打印
    std::this_thread::sleep_for(std::chrono::milliseconds(500)); 
    std::cout << "I'm function_1()" << std::endl;
}

void test() 
{
    std::thread t1(function_1);
    t1.detach();
    // t1.join();
    std::cout << "test() finished" << std::endl;
}

int main() 
{
    test();
    //让主线程晚于子线程结束
    std::this_thread::sleep_for(std::chrono::milliseconds(1000)); //延时1s
    return 0;
}

// 使用 t1.detach()时
// test() finished
// I'm function_1()

// 使用 t1.join()时
// I'm function_1()
// test() finished

  1. 由于线程入口函数内部有个500ms的延时,所以在还没有打印的时候,test()已经执行完成了,t1已经被析构了,但是它负责的那个线程还是能够运行,这就是detach()的作用;

  2. 如果去掉main函数中的1s延时,会发现只打印了test() finished,因为主线程执行的太快,整个程序已经结束了,后台线程被C++运行时库回收了;

  3. 如果将t1.detach()换成t1.join()test函数会在t1线程执行结束之后,才会执行结束。

  一旦一个线程被分离了,就不能够再被join了。如果非要调用,程序就会崩溃,可以使用joinable()函数判断一个线程对象能否调用join()

void test() 
{ std::thread t1(function_1); t1.detach();
if(t1.joinable()) t1.join(); assert(!t1.joinable()); }

线程类的构造函数

  std::thread类的构造函数是使用可变参数模板实现的,也就是说,可以传递任意个参数;第一个参数是线程的入口函数,而后面的若干个参数是该函数的参数第一个参数是一个可调用对象(Callable Objects),可以是以下几种情况:

  • 函数指针
  • 重载了operator()运算符的类对象,即仿函数
  • lambda表达式(匿名函数)
  • std::function
函数指针:
// 普通函数 无参
void function_1() 
{}

// 普通函数 1个参数
void function_2(int i) 
{}

// 普通函数 2个参数
void function_3(int i, std::string m) 
{}

std::thread t1(function_1);
std::thread t2(function_2, 1);
std::thread t3(function_3, 1, "hello");

t1.join();
t2.join();
t3.join();
 
仿函数:
// 仿函数
class Fctor 
{
public:
    void operator() () 
    {}
};

Fctor f;
std::thread t4{Fctor()}; //注意加花括号
  一个仿函数类生成的对象,使用起来就像一个函数一样,比如上面的对象f,当使用f()时就调用operator()运算符。所以也可以让它成为线程类的第一个参数,如果这个仿函数有参数,同样的可以写在线程类的后几个参数上。
 
lambda表达式:
//[]捕获列表,用于捕获上下文变量供lambda使用
//同时,编译器根据该符号可以判断接下来是lambda函数
//()参数列表,无参数的话可以省略
//返回值类型
//{}函数体
std::thread t1( [](){std::cout << "hello" << std::endl;} );

//“world”为参数m的值
std::thread t2([](std::string m)
{std::cout << "hello " << m << std::endl;}, "world");
std::function:
class A
{
public:
    void func1()
    {}

    void func2(int i)
    {}

    void func3(int i, int j)
    {}
};

A a;
//std::function<返回值类型(参数类型)>
//std::bind(函数对象,参数)
std::function<void(void)> f1 = std::bind(&A::func1, &a);
std::function<void(void)> f2 = std::bind(&A::func2, &a, 1);
std::function<void(int)> f3 = std::bind(&A::func2, &a, std::placeholders::_1);
std::function<void(int)> f4 = std::bind(&A::func3, &a, 1, std::placeholders::_1);
std::function<void(int, int)> f5 = std::bind(&A::func3, &a, std::placeholders::_1, std::placeholders::_2);

std::thread t1(f1);
std::thread t2(f2);
std::thread t3(f3, 1);
std::thread t4(f4, 1);
std::thread t5(f5, 1, 2);
 
原文地址:https://www.cnblogs.com/yongjin-hou/p/14527220.html