一点一滴成长

导航

boost--线程

 1、thread的使用

  boost的thread包含了线程创建、使用、同步等内容,使用thread需要包含头文件"boost\thread.hpp"。

  thread中使用了需要编译的thread库,所以还需要添加thread库到项目附加库目录,在linux下链接thread库时还需要使用-lpthread选项来链接posix线程库。

  如下所示,定义一个thread对象后,线程就开始执行。thread构造函数的第一个参数是一个函数或函数对象或function对象,剩余参数是传递给执行函数或函数对象的参数, 如果希望是引用传递的话则需要配合使用ref。

    std::thread([]{
            std::cout << "thread" << std::endl;
        }).detach();

 

  定义一个thread对象后,在该对象销毁之前需要调用join()(阻塞等待)或者timed_join()(指定等待时间)来释放相关资源,否则需要调用detach()来将线程与调用线程的关联分离,线程结束后由运行时库负责清理该线程相关的资源,detach()的功能类似于posix的pthread_detach()。创建一个线程后默认的状态是joinable,调用detach()将线程执行体分离后,成员函数joinable()会返回false,即线程的状态是非joinable。

  需要注意的是thread对象是不可拷贝的。

#include "boost\thread.hpp"

void PrintThreadFunc(const int& n, const string& str)
{
    cout << str << n << endl;
}

int main()
{    
    int n1 = 1;
    string str1 = "hello";
    boost::thread t1(PrintThreadFunc, ref(n1), ref(str1));

    int n2 = 2;
    string str2 = "boost";
    function<void()> fun = bind(PrintThreadFunc, ref(n2), ref(str2));
    boost::thread t2(fun);

    t1.timed_join(boost::posix_time::seconds(1)); //最多等待1秒
    t2.join(); //一直等待

    return 0;
}
View Code

 2、线程的一些操作

  可以调用thread的成员函数get_id()获得线程ID,线程ID提供了比较操作符和流输出操作,因此可以作为标准容器的元素。当一个线程的状态是joinable的,那么可以调用成员函数get_id()获得线程ID,线程ID提供了比较操作符和流输出操作,因此可以作为标准容器的元素。如果调用了成员函数detach()将线程执行体分离,或者thread线程还未开始,那么get_id()获得一个空的id,即thread::id()。如下所示,启动线程有三种方法,并且演示了使用thread的get_id()来判断线程是否已经运行:

 

std::thread g_thd;

void startThread2()
{
    if (g_thd.get_id() == std::thread::id()) { //线程还未运行
        
        g_thd = std::thread([] { // B方法启动线程:移动一个thread到另一个thread
            // ToDo
        });

        g_thd.swap(std::thread([] { //C方法启动线程:交换两个thread
            // ToDo
        }));
    }
}

int main()
{
    std::thread th1([](int n) { //A方法启动线程
        // ToDo
        }, 100);

    startThread2();
}

 

  静态函数this_thread::get_id()可以获得当前线程的线程ID。

  静态函数thread::this_thread::sleep()可以让当前线程睡眠一段时间或到指定时间,

  静态函数thread::hardware_concurrency()可以获得当前CPU的内核数量。

  静态函数this_thread::yield()指示当前线程放弃时间片,允许其他线程运行。

    boost::this_thread::sleep(boost::posix_time::seconds(2)); //睡眠2秒
    cout << boost::this_thread::get_id() << endl; //输出当前线程ID
    cout << boost::thread::hardware_concurrency() << endl; //输出CPU核心数
    boost::this_thread::yield(); //放弃当前CPU时间
View Code

 c++11中也有对应的操作,eg:

std::this_thread::sleep_for(std::chrono::seconds(5)); //睡眠5秒
std::this_thread::sleep_for(4ms); //睡眠4毫秒,需要引用命名空间:using namespace std::chrono

 3、线程中断

  thread的成员函数interrupt()设置正在执行的线程被中断,被中断的线程会抛出一个thread_interrupted异常,它不是std::exception或boost::exception的子类。thread_interrupted异常应该在线程执行函数里捕获并处理,如下所示,如果没有捕获处理这个异常,默认的动作是终止线程。

void ThreadFun()
try
{
    //函数体
}
catch (boost::thread_interrupted&)
{
    //异常处理
}
View Code

  线程其实不是任意时刻都能被中断的,只有当线程执行到中断点的时候才被中断,thread中的中断点有:thread::join()系列函数、thread::sleep()函数、condition_variable::wait()系列函数、this_thread::interruption_point()函数,其中this_thread::interruption_point()函数表示执行到本函数的时候就可以被中断。

  缺省情况下线程都是允许中断的,this_thread::interruption_enabled()函数可以检测当前线程是否允许中断,this_thread::interruption_requested()用来检测当前线程是否被要求中断。this_thread中的disable_interruption类是一个RAII类型的对象,它在构造的时候关闭线程的中断,析构的时候恢复线程的中断状态。

 4、线程组

  线程组thread_group用于管理一组创建的线程,成员函数create_thread()可以创建thread对象并运行线程,也可以创建thread对象后使用成员函数add_thread()来加入线程组。成员函数create_thread()的声明如下:

template<typename F>
thread* create_thread(F threadfunc);

  成员函数remove_thread()可以删除线程组里的thread对象,成员函数join_all()用来等待所有的thread对象,成员函数interrupt_all()用来中断所有的thread对象。

  使用示例:

    boost::thread_group tg;
    
    int n1 = 0;
    tg.create_thread(bind(ThreadFun1, n1, "c++"));
    int n2 = 0;
    tg.create_thread(bind(ThreadFun2, n2, "python"));

    tg.join_all();
View Code

5、future与async()

  如果想要获得异步函数的返回值,可以使用future。C++11中已经支持future。

  我们在编程中使用异步任务时,可以使用std::async()。std::future可以帮助我们在需要的时候获取异步任务的执行结果,如下所示:

#include <iostream>
#include <chrono>
#include <future>

int async_task() {
    std::this_thread::sleep_for(std::chrono::seconds(3));
    return 100;
}

int main() {
    std::future<int> futureResult = std::async(std::launch::async, async_task); //异步的执行async_task()

    try {
        int result = futureResult.get(); //future::get()方法会阻塞等待,直到异步操作完成或发生异常. 可以使用std::future::wait_for()或std::future::wait_until()等待一段时间或等到某个时刻
        std::cout << result << std::endl;  //3秒后输出100
    }
    catch (const std::exception& e) {
        std::cerr << "Exception caught: " << e.what() << std::endl;
    }
    catch (...) {
        std::cerr << "Unknown exception caught" << std::endl;
    }

    return 0;
}
View Code

   即使不需要关注异步任务返回值的话,也推荐使用一个future来保存std::async()的返回值,否则可能会出现问题,比如下面的代码,第二个异步任务会等待第一个异步任务执行完成后才开始执行。future只能移动不能拷贝,所以只能在一个线程中通过get()获得结果,如果想要多个线程能拿到结果,可以使用shared_future,通过future的shared()方法。

std::async(std::launch::async, [] { fun1(); });
std::async(std::launch::async, [] { fun2(); });

  std::async()还支持延迟绑定,即直到调用std::future的get()或wait()才执行任务,通过指定std::launch::deferred,如下所示。同时std::async()也支持向异步任务传入参数:

std::mutex m;
struct X
{
    void bar(const std::string& str)
    {
        std::lock_guard<std::mutex> lk(m);
        std::cout << str << '\n';
    }

    int operator()(int i)
    {
        std::lock_guard<std::mutex> lk(m);
        std::cout << i << '\n';
        return i + 10;
    }
};

int main(int argc, char** argv)
{
    X x;
    auto a1 = std::async(std::launch::deferred, &X::bar, x, "world!"); //Calls x.bar("world!") with deferred policy: prints "world!" when a2.get() or a2.wait() is called
    a1.wait();

    auto a2 = std::async(std::launch::async, X(), 43); //Calls X()(43); with async policy: prints "43" concurrently
    auto r = a2.get();

    return 0;
}
View Code

  如果不使用std::async()而是使用thread创建新线程来执行异步任务的话,可以使用promise+future来实现子线程通知,而且promise可以实现任务还未结束的时候就通知,即在异步任务里进行通知(类似事件对象,但多出了通知值):

#include <iostream>
#include <future>
#include <thread>

void task(std::promise<int> promiseResult) {
    std::this_thread::sleep_for(std::chrono::seconds(3));

    int result = 42;
    promiseResult.set_value(result); // 将结果设置到promise对象中,其关联的future会立即得到通知
}

int main() {
    std::promise<int> promiseResult;
    std::future<int> futureResult = promiseResult.get_future(); //获取promise关联的future

    std::thread task_thread(task, std::move(promiseResult));

    int result = futureResult.get();
    std::cout << result << std::endl;

    task_thread.join();

    return 0;
}
View Code

    官方对std::async()的说明是"runs the function asynchronously (potentially in a separate thread which might be a part of a thread pool)",可以看到其并不保证任务通过线程池来执行,所以我们也可以自己通过thread pool来实现异步任务,通过promise+future获得任务的返回值。

   以下是网上看到的关于std::async()与异常的问题,记录一下:

   

 

6、call_once()

  call_once()用来设置在多线程环境下指定的函数只被调用一次。

7、C++11中的线程

#include <thread>
void foo(int x)
{
    x = 0;
}

void bar(int& x)
{
    x = 0;
}

int main()
{    
    int n1 = 50, n2 = 100;
    std::thread first(foo, n1);
    std::thread second(bar, ref(n2));

    first.join(); //first.detach();
    second.join(); //second.detach();

    cout << n1 << endl; //n1为50
    cout << n2 << endl; //n2为0

    return 0;
}
View Code

  需要注意的几点:

   ①、可执行的thread对象必须在他被销毁之前被主线程join(调用thread对象的join())或者将其设置为 detached(调用thread对象的detach),否则会产生abort。

   ②、如果使用函数对象作为thread的参数的话,直接传入临时对象会出错,可以定义一个对象传入或者使用lambda表达式:

class CTask
{
public:
    void operator()()
    {
        int a = 0;
    }
};

//std::thread th1(CTask()); //直接传入临时对象会出错
CTask task;
std::thread th1(task);
th1.join();
View Code

   ③、传递给线程函数的参数是先保存在于一个中转站中,当函数执行的时候再传给函数的形参,而这个时候传递的参数指向的值很有可能已经失效,所以,对于线程函数传递的参数应该与形参类型相同,而不是再进行转换:

void task(int& a, string str)
    {

    }

    int iNum = 0;
    char* pStr = new char[100];
    strcpy(pStr, "test");
    //std::thread th(task, 5, std::ref(iNum), pStr); //不应该直接传入pStr,防止task中还未对参数str初始化成功pStr已被释放。
    std::thread th(task, std::ref(iNum), string(pStr)); //应该传入对应类型
    delete[] pStr;
    th.join();
View Code

   ④、如果线程函数的参数是引用的话传入时还需要使用ref包住传入的参数,否则也是值传递。

posted on 2017-09-29 09:28  整鬼专家  阅读(1779)  评论(0编辑  收藏  举报