【高性能计算】C++多线程计算与线程池

线程

在这里插入图片描述

  • 一个进程中可以并发多个线程,每条线程并行执行不同的任务。
  • 因为虚拟内存的关系不同的进程互相不知道对方的存在,相对独立,互相有独立的内存空间,地址空间;
  • 相同进程内的线程之间共享进程的内存空间,文件描述符表,部分寄存器等资源,但是每个线程都有自己独立的线程栈和部分寄存器,线程间访问资源需要考虑互斥问题。
  • 进程创建,销毁,切换代价大;
  • 线程创建,销毁,切换小。
  • 进程有利于资源管理和保护,开销大;
  • 线程不利于资源管理和保护,需要使用锁保证线程的安全运行,开销小
  • 不同进程间的线程通信等同于进程通信,一般是五种通信方式:
    消息队列;
    共享内存;
    有名管道/无名管道;
    信号;
    scoket。
  • 相同进程中的不同线程因为资源共享不存在通信问题,主要是资源的互斥访问,一般需要资源加锁以防死锁。

线程池

  • 线程池采用预创建的技术,在应用程序启动之后,将立即创建一定数量的线程(N1),放入空闲队列中。这些线程都是处于阻塞(Suspended)状态,不消耗CPU,但占用较小的内存空间。
  • 当任务到来后,缓冲池选择一个空闲线程,把任务传入此线程中运行。当N1个线程都在处理任务后,缓冲池自动创建一定数量的新线程,用于处理更多的任务。
  • 在任务执行完毕后线程也不退出,而是继续保持在池中等待下一次的任务。
  • 当系统比较空闲时,大部分线程都一直处于暂停状态,线程池自动销毁一部分线程,回收系统资源。
  • 线程池的主要组成部分有二:
    (1)任务队列(Task Queue)
    (2)线程池(Thread Pool)
  • 线程池通常适合下面的几个场合:
    (1)单位时间内处理任务频繁而且任务处理时间短
    (2)对实时性要求较高。如果接受到任务后在创建线程,可能满足不了实时要求,因此必须采用线程池进行预创建。
    一个分享,这里,代码非常的简洁,只有一个头文件ThreadPool.h,这里贴出来作为备份。
#ifndef THREAD_POOL_H
#define THREAD_POOL_H
 
#include <vector>
#include <queue>
#include <memory>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <functional>
#include <stdexcept>
 
class ThreadPool {
public:
    ThreadPool(size_t);
    template<class F, class... Args>
    auto enqueue(F&& f, Args&&... args) 
        -> std::future<typename std::result_of<F(Args...)>::type>;
    ~ThreadPool();
private:
    // need to keep track of threads so we can join them
    std::vector< std::thread > workers;
    // the task queue
    std::queue< std::function<void()> > tasks;
    
    // synchronization
    std::mutex queue_mutex;
    std::condition_variable condition;
    bool stop;
};
 
// the constructor just launches some amount of workers
inline ThreadPool::ThreadPool(size_t threads)
    :   stop(false)
{
    for(size_t i = 0;i<threads;++i)
        workers.emplace_back(
            [this]
            {
                for(;;)
                {
                    std::function<void()> task;
 
                    {
                    	// 锁定互斥锁以确保没有其他人正在访问该资源
                        std::unique_lock<std::mutex> lock(this->queue_mutex);
                        this->condition.wait(lock,
                            [this]{ return this->stop || !this->tasks.empty(); });
                        if(this->stop && this->tasks.empty())
                            return;
                        task = std::move(this->tasks.front());
                        this->tasks.pop();
                    }
 
                    task();
                }
            }
        );
}
 
// add new work item to the pool
template<class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args) 
    -> std::future<typename std::result_of<F(Args...)>::type>
{
    using return_type = typename std::result_of<F(Args...)>::type;
 	// 创建一个执行
    auto task = std::make_shared< std::packaged_task<return_type()> >(
            std::bind(std::forward<F>(f), std::forward<Args>(args)...)
        );
        
    std::future<return_type> res = task->get_future();
    {
        std::unique_lock<std::mutex> lock(queue_mutex);
 
        // don't allow enqueueing after stopping the pool
        if(stop)
            throw std::runtime_error("enqueue on stopped ThreadPool");
 
        tasks.emplace([task](){ (*task)(); });
    }
    condition.notify_one();
    return res;
}
 
// the destructor joins all threads
inline ThreadPool::~ThreadPool()
{
    {
        std::unique_lock<std::mutex> lock(queue_mutex);
        stop = true;
    }
    condition.notify_all();
    for(std::thread &worker: workers)
        worker.join();
}
 
#endif
#include <iostream>
#include "ThreadPool.h"
 
void func()
{
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::cout<<"worker thread ID:"<<std::this_thread::get_id()<<std::endl;
}
 
int main()
{
    ThreadPool pool(4);
    while(1)
    {
       pool.enqueue(fun);
    }
}

锁与线程

参考这个较为详细,这里

  • 死锁:当两个以上的运算单元,双方都在等待对方停止运行,以获取系统资源,但是没有一方提前退出时,就称为死锁。
  • 死锁的基本条件:
    禁止抢占(no preemption):系统资源不能被强制从一个进程中退出;
    持有和等待(hold and wait):一个进程可以在等待时持有系统资源;
    互斥(mutual exclusion):资源只能同时分配给一个进程或者线程,无法多个进程或者线程共享;
    循环等待(circular waiting):一系列进程互相持有其他进程所需要的资源。
  • 死锁的四个条件缺一不可,一般如果需要解决死锁,破坏其中一个即可。

版权声明:本文为heroybc原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。