C++并发与多线程 2_线程启动结束,创建线程多种方法,join,detach

Posted TianSong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++并发与多线程 2_线程启动结束,创建线程多种方法,join,detach相关的知识,希望对你有一定的参考价值。

演示线程运行的开始和结束

  • 可执行程序运行得到一个进程,进程中唯一的主线程启动,当主线程从 main 函数返回,整个进行结束。
  • 创建的子线程需要从一个初始函数开始运行,一旦函数执行完毕,当前子线程结束。
  • 进程结束标志:主线程是否结束。如果主线程执行完毕,则代表整个进程结束,一般情况下此时如果还有其它子线程未执行完,则子线程会被强行终止(例外:detach)。

thread

创建一个线程执行对象。

join

阻塞主线程(调用线程),直到子线程执行结束。

注:需要保证在 joinable 返回 true 时使用(返回 false 时运行时抛出 std::system_error 异常)

detach

将子线程和主线程的关联分离,子线程可驻留系统后台独立继续运行,主线程无法再获得主线程的控制权,即主线程结束,子线程也不会结束。当子线程结束时,由C++运行时库负责清理线程相关的资源。

注:需要保证在 joinable 返回 true 时使用(返回 false 时运行时抛出 std::system_error 异常)

joinable

判断一个线程对象是否能够调用 join() 或者 detach(),可以返回 true,不可以返回 false。

Test1: thread, join, joinable

#include <iostream>
#include <thread>

using namespace std;

void thread_func()
{
    cout << "thread_func begin" << endl;

    for (uint32_t i=0; i<10000; ++i)
    { }

    cout << "thread_func end" << endl;
}

int main()
{
    cout << "main begin" << endl;

    thread my_thread(thread_func);

    if (my_thread.joinable())   // 注: 调用 join 前需要 joinable 判断!!! 
    {
        cout << "my_thread.joinable() " << my_thread.joinable() << endl;

        my_thread.join();
    }

    cout << "my_thread.joinable() " << my_thread.joinable() << endl;

    cout << "main end" << endl;

    return 0;
}

输出:

main begin
my_thread.joinable() 1
thread_func begin
thread_func end
my_thread.joinable() 0
main end

Test2: detach

#include <iostream>
#include <thread>

using namespace std;

void thread_func()
{
    cout << "thread_func begin" << endl;

    for (uint32_t i=0; i<10000; ++i)
    { }

    cout << "thread_func end" << endl;
}

int main()
{
    cout << "main begin" << endl;

    thread my_thread(thread_func);

    if (my_thread.joinable())
    {
        my_thread.detach();
    }

    cout << "main end" << endl;

    return 0;
}

输出: 【进程结束,子线程后台运行,无法再在控制台输出】

main begin
main end

其它创建线程的方法

线程参数是一个可调用对象。其中可调用对象包含:函数、函数指针、lambda表达式、std::bind创建的对象、std::function创建的对象以及重载了函数调用运算符的类对象。

#include <iostream>
#include <thread>

using namespace std;

void thread_func()
{
    cout << "thread_func begin" << endl;

    for (uint32_t i=0; i<10000; ++i)
    { }

    cout << "thread_func end" << endl;
}

auto thread_lambda = []{
    cout << "thread_lambda begin" << endl;

    for (uint32_t i=0; i<10000; ++i)
    { }

    cout << "thread_lambda end" << endl;
};

class thread_class {
public:
    thread_class() {
        cout << "thread_class " << this << endl;
    }

    thread_class(const thread_class&) {
        cout << "thread_class(const thread_class&) " << this << endl;
    }

    ~thread_class() {
        cout << "~thread_class() " << this << endl;
    }

    void operator () () {
        cout << "thread_class begin" << endl;

        for (uint32_t i=0; i<10000; ++i)
        { }

        cout << "thread_class end" << endl;
    }
};

class base {
public:
    void func()
    {
        cout << "base::func begin" << endl;

        for (uint32_t i=0; i<10000; ++i)
        { }

        cout << "base::func end" << endl;
    }
};

int main()
{
    cout << "main begin" << endl;

    cout << "function:" << endl;
    thread my_thread1(thread_func);
    if (my_thread1.joinable())
        my_thread1.join();

    cout << "lambda:" << endl;
    thread my_thread2(thread_lambda);
    if (my_thread2.joinable())
        my_thread2.join();

    cout << "class:" << endl;
    thread_class tc;
    thread my_thread3(tc);  // 注意:此处的线程对象中拥有可调用类对象副本!!
    if (my_thread3.joinable())
        my_thread3.join();

    cout << "class::function:" << endl;
    base b;
    thread my_thread4(&base::func, &b);
    if (my_thread4.joinable())
        my_thread4.join();

    cout << "main end" << endl;

    return 0;
}

输出:

main begin
function:
thread_func begin
thread_func end
lambda:
thread_lambda begin
thread_lambda end
class:
thread_class 0x62fde7
thread_class(const thread_class&) 0x62fd77
thread_class(const thread_class&) 0x6917f8
~thread_class() 0x62fd77
thread_class begin
thread_class end
~thread_class() 0x6917f8
class::function:
base::func begin
base::func end
main end
~thread_class() 0x62fde7
问题:自定义可调用类对象测试代码中拷贝构造函数为什么被调用了两次?
在测试环境QT5.15.2的STL源码中可见
// 1. thread 使用万能引用进行构造
template<typename _Callable, typename... _Args>
      explicit
      thread(_Callable&& __f, _Args&&... __args)
      {
#ifdef GTHR_ACTIVE_PROXY
    // Create a reference to pthread_create, not just the gthr weak symbol.
    auto __depend = reinterpret_cast<void(*)()>(&pthread_create);
#else
    auto __depend = nullptr;
#endif
        _M_start_thread(_S_make_state(
          __make_invoker(std::forward<_Callable>(__f),
                 std::forward<_Args>(__args)...)),
        __depend);
      }
      
// 2. __make_invoker 使用 __make_invoker      
template<typename _Callable, typename... _Args>
      static _Invoker<__decayed_tuple<_Callable, _Args...>>
      __make_invoker(_Callable&& __callable, _Args&&... __args)
      {
    return { __decayed_tuple<_Callable, _Args...>{
        std::forward<_Callable>(__callable), std::forward<_Args>(__args)...
    } };
      }

// 3. __decayed_tuple 是 tuple 别名,构造了 tuple 对象,发生一次拷贝构造
template<typename... _Tp>
      using __decayed_tuple = tuple<typename std::decay<_Tp>::type...>;
      
// 4. new _Impl{...},发生第二次拷贝构造
template<typename _Callable>
  static _State_ptr
  _S_make_state(_Callable&& __f)
  {
using _Impl = _State_impl<_Callable>;
return _State_ptr{new _Impl{std::forward<_Callable>(__f)}};
  }
注:用可调用类对象创建的线程对象拥有类对象的副本!!这将触发拷贝动作,性能可能是受损失的。

以上是关于C++并发与多线程 2_线程启动结束,创建线程多种方法,join,detach的主要内容,如果未能解决你的问题,请参考以下文章

C++并发与多线程 4_创建多个线程数据共享问题分析

C++并发与多线程 3_线程传参数详解,detach 注意事项

C++并发与多线程 10_shared_futureautomic

C++并发与多线程 9_asyncfuturepackaged_taskpromise

C++并发与多线程 8_condition_variablewaitnotify_onenotify_all

C++并发与多线程 11_std::atomic叙谈std::launch(std::async) 深入