从主线程C 11执行函数调用的线程

thread that executes function calls from a main thread c++11

本文关键字:线程 函数调用 执行      更新时间:2023-10-16

我想实现一个可以从主线程中接受函数指针并串行执行的线程。我的想法是使用一个将功能指针及其对象保持的结构,并继续将其推到队列。这可以封装在班级中。然后,任务线程可以从队列中弹出并进行处理。我还需要同步它(因此它不会阻止主线程吗?(,所以我正在考虑使用信号量。尽管我对程序的结构有一个不错的想法,但我在编码此编码方面遇到了麻烦,尤其是C 11中的线程和信号量同步。如果有人可以提出一个我可以实施此功能的大纲,那就太好了。

编辑:重复的问题回答了有关创建线程池的问题。看来正在创建多个线程来完成一些工作。我只需要一个可以排队函数指针并按照收到的顺序进行处理的线程。

检查此代码段,我已经实现了不使用类。看看它是否有帮助。可以在这里避免有条件变量,但是我希望读者线程仅在作者发出信号时才进行轮询,以便不会浪费读者中的CPU周期。

#include <iostream>
#include <functional>
#include <mutex>
#include <thread>
#include <queue>
#include <chrono>
#include <condition_variable>
using namespace std;
typedef function<void(void)> task_t;
queue<task_t> tasks;
mutex mu;
condition_variable cv;
bool stop = false;
void writer()
{
    while(!stop)
    {
        {
            unique_lock<mutex> lock(mu);
            task_t task = [](){ this_thread::sleep_for(chrono::milliseconds(100ms));   };
            tasks.push(task);
            cv.notify_one();
        }
        this_thread::sleep_for(chrono::milliseconds(500ms)); // writes every 500ms
    }
}
void reader()
{
    while(!stop)
    {
        unique_lock<mutex> lock(mu);
        cv.wait(lock,[]() { return !stop;});  
        while( !tasks.empty() )
        {
            auto task = tasks.front();            
            tasks.pop();
            lock.unlock();
            task();
            lock.lock();
        }
    }
}
int main()
{
    thread writer_thread([]() { writer();}  );
    thread reader_thread([]() { reader();}  );
    this_thread::sleep_for(chrono::seconds(3s)); // main other task
    stop = true;

    writer_thread.join();
    reader_thread.join();
}

您的问题有2个部分。以线程安全方式存储工作列表并操纵作业列表。

在第一部分中,查看std::functionstd::bindstd::ref

在第二部分中,这类似于生产者/消费者问题。您可以使用std::mutexstd::condition_variable实现信号量。

有一个提示/轮廓。现在我的完整答案...

步骤1(

将您的功能指针存储在std ::功能的队列中。

std::queue<std::function<void()>>

队列中的每个元素都是一个不采用参数并返回void的函数。

对于接受参数的函数,请使用 std::bind绑定参数。

void testfunc(int n);
...
int mynum = 5;
std::function<void()> f = std::bind(testfunction, mynum);

调用F时,即f()5将作为参数1传递给testfuncstd::bind立即按值复制mynum

您可能也希望能够通过参考传递变量。这对于从功能中恢复结果以及传递共享同步设备(如信号量和条件(很有用。使用std::ref,参考包装器。

void testfunc2(int& n);  // function takes n by ref
...
int a = 5;
std::function<void()> f = std::bind(testfunction, std::ref(a));

std::functionstd::bind可以与任何可呼叫(功能函数或lambdas(一起使用 - 这很整洁!

步骤2(

队列不空时的工人线程排名。您的代码看起来应该与生产者/消费者问题相似。

class AsyncWorker
{
    ...
public:
    // called by main thread
    AddJob(std::function<void()> f)
    {
        {
            std::lock_guard<std::mutex> lock(m_mutex);
            m_queue.push(std::move(f));
            ++m_numJobs;
        }
        m_condition.notify_one();  // It's good style to call notify_one when not holding the lock. 
    }
private:
    worker_main()
    {
        while(!m_exitCondition)
            doJob();
    }
    void doJob()
    {
        std::function<void()> f;
        {
            std::unique_lock<std::mutex> lock(m_mutex);
            while (m_numJobs == 0)
                m_condition.wait(lock);
            if (m_exitCondition)
                return;
            f = std::move(m_queue.front());
            m_queue.pop();
            --m_numJobs;
        }
        f();
    }
    ...

注意1:同步代码...带有m_mutexm_conditionm_numJobs ...基本上是您在C '11中实现信号量所必须使用的。我在这里所做的比使用单独的信号量类更有效,因为只有1个锁定。(信号量将有自己的锁,您仍然必须锁定共享队列(。

注2:您可以轻松添加其他工作线程。

注3: M_ExitCondition在我的示例中是std::atomic<bool>

实际上以多态性方式设置AddJob函数进入C '11 variadic模板和完美的转发...

class AsyncWorker
{
    ...
public:
    // called by main thread
    template <typename FUNCTOR, typename... ARGS>
    AddJob(FUNCTOR&& functor, ARGS&&... args)
    {
        std::function<void()> f(std::bind(std::forward<FUNCTOR>(functor), std::forward<ARGS&&>(args)...));
        {
            std::lock_guard<std::mutex> lock(m_mutex);
            m_queue.push(std::move(f));
            ++m_numJobs;
        }
        m_condition.notify_one();  // It's good style to call notify_one when not holding the lock. 
    }

我认为,如果您只是使用逐个值而不是使用转发引用,但我尚未对此进行测试,而我知道完美的转发非常有效。避免完美的转发可能会使概念略有混乱,但代码不会有很大不同...