我想实现一个可以接受来自主线程的函数指针并以串行方式执行它们的线程。我的想法是使用一个结构来保存函数指针及其对象并继续将其推送到队列中。这可以封装在一个类中。然后,任务线程可以从队列中弹出并处理它。我还需要同步它(所以它不会阻塞主线程?),所以我在考虑使用信号量。虽然我对程序的结构有一个不错的了解,但我在编写代码时遇到了麻烦,尤其是C ++ 11中的线程和信号量同步。如果有人可以提出一个我可以实现这一目标的大纲,那就太棒了。
编辑:重复的问题回答了有关创建线程池的问题。看起来正在创建多个线程来完成一些工作。我只需要一个可以对函数指针进行排队的线程,并按接收顺序对它们进行处理。
答案 0 :(得分:2)
检查此代码段,但我已经实现了,但没有使用类。看看它是否有所帮助。这里可以避免使用条件变量,但是我希望读者线程只在来自编写器的信号时进行轮询,这样读者的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();
}
答案 1 :(得分:1)
您的问题有2个部分。以线程安全的方式存储作业列表和操作作业列表。
对于第一部分,请查看std::function
,std::bind
和std::ref
。
对于第二部分,这类似于生产者/消费者问题。您可以使用std::mutex
和std::condition_variable
实现信号量。
有一个提示/大纲。现在我的完整答案......
步骤1)
将函数指针存储在std :: function的队列中。
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传递给testfunc
。 std::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::function
和std::bind
可以使用任何callables - 函数,仿函数或lambda - 这非常简洁!
第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_mutex
,m_condition
和m_numJobs
...基本上就是你必须用来实现的C ++ '11中的信号量。我在这里做的比使用单独的信号量类更有效,因为只锁定了1个锁。 (信号量有自己的锁,你仍然需要锁定共享队列。)
注2:您可以轻松添加其他工作线程。
注3:我的示例中的m_exitCondition是std::atomic<bool>
实际上以多态方式设置AddJob
函数会进入C ++'11可变参数模板并完美转发......
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.
}
我认为如果你只是使用pass-by-value而不是使用转发引用它可能会有效,但我没有测试过这个,而我知道完美的转发效果很好。避免完美转发可能会使概念稍微混乱,但代码不会有太大差异......