天天看点

C++多线程之旅实战-线程池的理解

前言

在很多公司小组都需要使用会议室进行讨论,但是每个小组都配备一个会议室又会很浪费。所以就将所有的会议室都拿出来放到一起,那个小组需要就像行政部门申请即可,根据申请的先后顺序使用会议室。使用完成以后自动归还,无需派专人进行管理。

基于这一思路,我们也可以把线程资源放到一个区域,然后根据每个用户的需求分配线程资源。并且还可以实现自动化的线程资源分配。

设计线程池有几个关键的问题:第一,线程中应该创建几个工作线程;第二,是否应该等待线程执行结束…

第一个线程

C++多线程之旅实战-线程池的理解

submit

不断提交任务,然后threads获取其中任务然后执行。这就是线程池的主要执行原理。

#include <thread>
#include <atomic>
#include <queue>
#include <vector>
#include <iostream>
class join_threads
{
    std::vector<std::thread> &threads;
public:
    explicit join_threads(std::vector<std::thread> &threads_):threads(threads_){}
    ~join_threads()
    {
        for(unsigned long i = 0 ; i < threads.size();++i)
        {
            if(threads[i].joinable())
                threads[i].join();
        }
    }
};

class thread_pool {
    std::atomic_bool done;
    std::queue<std::function<void()> > work_queue;
    std::vector<std::thread> threads;
    join_threads joiner;

    void work_thread() {
        while (!done) {
            std::function<void()> task;
            task = work_queue.front();
            work_queue.pop();
            if (task) {
                task();
            } else {
                std::this_thread::yield();
            }
        }
    }
public:
    thread_pool() : done(false), joiner(threads) {
        unsigned const thread_count = std::thread::hardware_concurrency();

        try {
            for (unsigned i = 0; i < thread_count; ++i) {
                threads.push_back(std::thread(&thread_pool::work_thread, this));
            }
        } catch (...) {
            done = true;
            throw;
        }
    }

    ~thread_pool() {
        done = true;
    }

    template<typename FunctionType>
    void submit(FunctionType f) {
        work_queue.push(std::function<void() >(f));
    }
};

void fun() {
    std::cout << "hello world" << std::endl;
}

int main() {
    thread_pool pool;
//    std::cout << "123";
    pool.submit(fun);
    pool.submit(fun);
    pool.submit(fun);
    pool.submit(fun);
    pool.submit(fun);
    pool.submit(fun);
    pool.submit(fun);
    pool.submit(fun);
    pool.work_thread();
}
           

这就是简单实现的线程池,通过

submit()

提交数据,调用

work_thread()

函数执行。

这就类似每天晚上小组提交第二天会议室申请表,然后到了第二天直接分配会议室使用权。这样就可以实现,例如在一些抢票或者需要快速反应的多线程情况下,就可以使用线程池。

thread_pool() : done(false), joiner(threads) {
	unsigned const thread_count = std::thread::hardware_concurrency();
	try {
		for (unsigned i = 0; i < thread_count; ++i) {
			threads.push_back(std::thread(&thread_pool::work_thread, this));
		}
	} catch (...) {
		done = true;
		throw;
	}
}
           

根据当前的处理的线程数,创建线程,将这一线程放入到

threads

容器中。利用

done

指示是否线程池完成操作,当

done

为false时,暂未完成线程池,为true时则完成线程池。

void work_thread() {
	while (!done) {
		std::function<void()> task;
		task = work_queue.front();
		work_queue.pop();
		if (task) {
			task();
		} else {
			std::this_thread::yield();
		}
	}
}
           

work_thread()

从队列中取出待执行的函数,然后执行函数。

但是上面这个函数我也不知道为什么始终没办法编译通过,可以去看这位同僚的博客。

如果大家能够告诉我我的代码那个地方存在问题我将万分感谢!

避免乒乓缓存

每次线程调用

submit

时,会向共享队列添加一个新元素,类似的工作线程会不停的从队列中取出元素来执行,这意味着处理器数目的增加会导致工作队列竞争。

避免的方法主要就是给每一个线程都使用一个单独的工作队列,将任务放到自己的队列中。

总结

这一部分内容主要需要动手实现,虽然在很多语言中线程池已经写成模块直接调用执行了。但是还是要知道其中的原理,以免在面试中遇到这样的问题,搞得瓜兮兮的!