在运行之前获取 std::thread 的 thread:id?

Posted

技术标签:

【中文标题】在运行之前获取 std::thread 的 thread:id?【英文标题】:Get std::thread's thread:id before it runs? 【发布时间】:2018-08-10 22:35:15 【问题描述】:

我正在尝试在 C++ 11 的 std::thread 之上构建一个线程安全层,其中每个对象都分配给一个拥有的线程,并且某些调用在错误的线程上使用时会引发硬错误。拥有线程是唯一可以将对象传输到另一个线程的线程。

一切正常,除了,我找不到在线程实际运行之前获取线程thread::id 的方法。而且我需要在交付对象之前将新线程的 ID 附加到对象上。

如果我使用

std::thread newThread( [theObject]()
                       
                           // use theObject here.
                        );

我能得到线程ID的最早点是在线程对象定义之后,此时线程已经在运行了。

我看到std::thread 有一个默认构造函数,但是我看不到一种方法可以给它一个函数,以便之后在线程上运行。

有没有办法在线程上执行两步构造,或者在创建时控制线程的ID?

【问题讨论】:

编写线程函数使其立即阻塞,当构造函数完成并且知道 id 时由主线程释放,这是很常见的。 你可以存储线程的地址而不是id吗?然后,如果您仍然需要id。您始终可以从 地址 (thread->get_id()) 获得它。 @Galik 这是同样的问题,只是形状不同。在构造函数返回之前我无法获取地址,此时线程已经在运行并且可能已经遇到需要知道线程 ID 的代码。 【参考方案1】:

您可以考虑让线程执行的函数在起飞前进行一些初始设置,而不是在线程开始运行之前获取它的 ID。例如,您可以这样做:

bool isReady = false;
bool wasReceived = false;
std::mutex mutex;
std::condition_variable condition;

std::thread newThread([theObject, &isReady, &mutex, &condition] 
   /* Wait until we've been cleared to go. */
   std::unique_lock<std::mutex> lock(isReady);
   condition.wait(lock, [&isReady]  return isReady; );

   /* Signal that we're done. */
   wasReceived = true;
   lock.unlock();
   condition.notify_one();

   /* Put code here to do whatever it is that the thread should do. */
);

/* Read the thread's ID. It's currently waiting for us. */
auto id = newThread.get_id();

/* Tell the thread that we're ready for it. */
std::unique_lock<std::mutex> lock(mutex);
isReady = true;
condition.notify_one();

/* Wait until the thread has confirmed that it's ready. */
condition.wait(lock, [&]  return wasReceived; );

这会创建线程并让它坐下来等待,直到创建者有机会读取其 ID。一旦发生这种情况,创建者就会等待,直到线程确认它已准备就绪,然后您可以根据需要使用线程 ID。

请注意上述代码中的错误 - 它完全未经测试。 :-)

【讨论】:

谢谢,这是一个很好的方法,甚至更好的解释。【参考方案2】:

不——一旦你创建了一个线程,它就会开始运行。如果您想在它(大部分)执行任何操作之前获取它的 ID,您可能需要创建一个小包装器,在其中向线程(例如)传递一个 CV 和一个存放其输出的队列。

然后当线程启动时,它会检索自己的 ID,将其存放在输出队列中,然后在 CV 上等待。当父母检索到 ID 并准备好让孩子开始做某事时,它会向 CV 发出信号,然后关闭它。

【讨论】:

什么是CV?即使从网络搜索结果中删除“履历”,我也没有找到任何适合此答案的扩展。【参考方案3】:

通过传递一个唯一的 std::promise 参数来启动每个处于非活动状态的线程,首先获取线程 id(线程 id 用作传递引用参数)然后让它等待线程设置的承诺经理。这也将消除使用条件变量的麻烦。

已编辑片段

class smart_thread 
public:
    smart_thread(std::function<void(void)> task)
    
        thread_ = std::thread([=]() 
            id_ = std::this_thread::get_id();
            // wait here till activated
            future_.get();
            if(active_) task();
        );
    

    void activate()  
        promise_.set_value();
        active_ = true;
    

    ~smart_thread() 
        if(!active_) promise_.set_value();
        thread_.join();
        
private:
    std::thread::id id_;
    std::atomic<bool> active_ = false;
    std::thread thread_;
    std::promise<void> promise_;
    std::future<void> future_ = promise_.get_future();
;

void main()

    auto task = []()  std::cout << "Hello World\n"; ;
    smart_thread thread(task); // start thread inactive mode
    thread.activate(); // activate thread

【讨论】:

【参考方案4】:

是否可以创建一个模板类,以std::function&lt;void(T *object)&gt; 的形式接受线程例程。如果需要传入其他参数,这可以通过匿名闭包轻松完成。

template <class T>
class ThreadWrapper

public:
    ThreadWrapper(std::function<void(T *object)> function, T *object) :
    
        m_thread = std::thread(WrapFunction, function, object);
        //optionally
        m_thread.detach();
    
    static void WrapFunction(ThreadWrapper *wrapper, std::function<void()> function, T *object)
    
        // Get the thread id and save in the object
        object->SetThreadId(get_id());
        // Now actually invoke the thread routine, with the id already installed.
        function(object);
    


// Cleanup is left as an exercise for the reader.

请注意上述代码中的错误 - 它完全未经测试。 :-) :-)

【讨论】:

这在我的情况下不起作用。我希望源线程是唯一允许修改对象所属线程的线程。 WrapFunction 已经在新线程上运行,因此可能会出现竞争条件,即源线程将相同的对象提供给两个线程,然后它们中的哪一个得到它是随机的。如果只有拥有的线程可以转移所有权,在第一次转移对象后,第二次执行将立即在发生第二次转移的地方产生错误,而不是在第一个线程尝试使用该对象后转移已发生。 @uliwitness 我明白了。是的,这是一个问题,我没有考虑过当前拥有的线程试图快速连续两次传递其拥有的对象的情况。我是从“单一所有权”的角度来看待这一点的,其中释放对象的线程应该认为一旦ThreadWrapper 的构造完成,它就不再拥有该对象。有点类似于将unique_ptr&lt;&gt; 分配给另一个所有者。 ... @uliwitness ... 一个可能的解决方案是在创建线程后立即复制在ThreadWrapper ctor 中的对象上使用SetThreadId 的代码。 ThreadWrapper ctor 还需要在做任何事情之前检查对象的所有权,如果它不拥有它,则抛出异常。也就是说,您仍然需要对SetThreadId 进行两次调用。一种是避免释放线程中的竞争条件,另一种是避免获取线程中的竞争条件。这也提出了一个问题,即在尝试双切换的情况下应该如何检测故障? 我使用的是std::atomic::compare_exchange_strong(),所以如果发送者传入它自己的线程 ID 作为预期的当前值,目标线程作为新值,它会自动告诉我我们是否不再是第二次分配尝试发生地点的所有者。

以上是关于在运行之前获取 std::thread 的 thread:id?的主要内容,如果未能解决你的问题,请参考以下文章

std::thread::join 无限期地阻塞在 main 之外

错误:静态断言失败:std::thread 参数在转换为右值后必须是可调用的

如何获取当前线程的 std::thread?

在带有标志选项 -m32 的 gcc-8.2.2 上找不到 std::thread。我正在使用 mingw

RAII 封装的 thread

从 std::thread 获取返回码? [复制]