我有一个带有空闲线程的线程池,等待在Windows应用程序中将作业推送到队列。
我的主应用程序线程中有一个循环,它顺序地向池中添加1000个作业(它添加一个作业,然后等待作业完成,然后添加另一个作业,x1000)。所以没有真正的并行处理发生......这里有一些伪代码:
////threadpool:
class ThreadPool
{
....
std::condition_variable job_cv;
std::condition_variable finished_cv;
std::mutex job_mutex;
std::queue<std::function <void(void)>> job_queue;
void addJob(std::function <void(void)> jobfn)
{
std::unique_lock <std::mutex> lock(job_mutex);
job_queue.emplace(std::move(jobfn));
job_cv.notify_one();
}
void waitForJobToFinish()
{
std::unique_lock<std::mutex> lock(job_mutex);
finished_cv.wait(lock, [this]() {return job_queue.empty(); });
}
....
void threadFunction() //called by each thread when it's first started
{
std::function <void(void)> job;
while (true)
{
std::unique_lock <std::mutex> latch(job_mutex);
job_cv.wait(latch, [this](){return !job_queue.empty();});
{
job = std::move(job_queue.front());
job_queue.pop();
latch.unlock();
job();
latch.lock();
finished_cv.notify_one();
}
}
}
}
...
////main application:
void jobfn()
{
//do some lightweight calculation
}
void main()
{
//test 1000 calls to the lightweight jobfn from the thread pool
for (int q = 0; q < 1000; q++)
{
threadPool->addJob(&jobfn);
threadPool->waitForJobToFinish();
}
}
所以基本上发生的事情是将一个作业添加到队列中并且主循环开始等待,然后等待的线程将其拾取,并且当线程完成时,它通知应用程序主循环可以继续,另一个作业可以添加到队列等。这样就可以顺序处理1000个作业。
值得注意的是,这些工作本身很小,可以在几毫秒内完成。
然而,我发现了一些奇怪的事情......
循环完成所需的时间基本上是O(n),其中n是线程池中的线程数。因此,即使在所有方案中一次一个地处理作业,10个线程池完成整个1000个作业任务所需的时间比单个线程池长10倍。
我试图找出原因,而我到目前为止唯一的猜测是上下文切换是瓶颈...当只有一个线程抓取作业时,可能需要更少(或零?)上下文切换开销。 ..但是当10个线程不断轮流处理一个单独的作业时,需要进行一些额外的处理吗?但这对我来说没有意义......对于一个工作来说解锁线程A所需要的操作是不一样的,因为它将是线程B,C,D ......?是否有一些操作系统级别的缓存正在进行,一个线程在没有给出不同的线程之前不会丢失上下文?因此,反复调用同一个线程比按顺序调用线程A,B,C要快吗?
但是在这一点上完全猜测......也许其他人可以对我获得这些结果的原因有所了解......直观地我假设只要只有1个线程在执行一段时间,我可以拥有一个任意大量线程的线程池,[x]作业的总任务完成时间也是一样的(只要每个作业相同且作业总数相同)。 ..为什么这是错的?
答案 0 :(得分:0)
你的“猜测”是正确的;这只是一个资源争用问题。
你的10个线程没有空闲,他们正在等待。这意味着操作系统必须遍历应用程序的当前活动线程,这意味着可能发生上下文切换。
活动线程被推回,一个“等待”线程被拉到前面,其中代码检查信号是否已被通知并且可以获取锁定,因为它可能不会在时间片中线程,它继续迭代剩余的线程,每个线程都试图查看是否可以获取锁,但它不能,因为你的“活动”线程还没有分配完成的时间片。
单线程池没有此问题,因为在操作系统级别不需要迭代其他线程;如果被授予,单线程池仍然比仅调用job
1000次慢。
希望可以提供帮助。