线程、锁、线程池

来源:互联网 发布:验血单哪项数据看男女 编辑:程序博客网 时间:2024/05/01 18:17

写在前面

最近看了线程池的原理,也学到不少知识,现做出总结,同时也实现了自己的线程池。

线程(Thread)

在任何一个时间点,线程有2种状态(这里与新建、就绪、执行、阻塞和死亡5种状态是两类不同的概念):

  • 可结合的(joinable)
    thread.join,创建线程有回收和杀死线程的权利,创建线程会被阻塞,直到线程执行完。
  • 分离的(detached)
    thread.detach,创建线程与线程分离,不具有控制线程的权利,两个线程独立运行,最后线程资源会由系统自动释放。

锁(Lock)

有四类锁。

  • 互斥锁
    线程获取不到锁便阻塞。
  • 自旋锁
    线程循环获取锁,不会阻塞,适用于临界区执行时间短(能很快获取锁)的程序,有效避免了线程调度浪费的时间,但一直占用CPU。
  • 条件变量
    与锁的概念不同,而是等待某个条件满足,经常用于线程间同步,比如线程A的条件依赖与线程B的设置,那么在线程B设置后,会通知阻塞的线程A向下执行。
  • 读写锁
    假设现在读锁锁定了临界区,那么后来的读锁依然能够进入临界区,这样可以允许并行读取公共资源(读锁锁住的临界区只允许读操作),如果后来的是写锁,那么写锁阻塞,如果在这之后再来读锁,那么读锁也阻塞,这样做是为了防止读锁一直占用公共资源(写锁饥饿);
    假设现在写锁锁定了临界区(写锁锁住的临界区允许写操作),后来的无论是读锁还是写锁,全部会阻塞。

线程池(ThreadPool)

在后台处理大量请求时,可能为每个请求创建一个线程,但是如果线程的创建和销毁比处理时间还长,那么对于频繁的创建线程是没有必要的,一般会用线程池去处理这样的请求。接下来会讲解如何用C++实现简单的线程池。线程池会固定线程数,利用生产者/消费者模型去处理任务。

  • 任务接口
    将任务抽象成接口,具体任务继承至该接口。

    class Task{protected:    std::string name;    void* data; //任务的一些属性public:    Task(std::string, void*);    virtual void run() = 0;//处理方法    virtual ~Task();};
  • 生产者/消费者模型
    生产者用来产生数据,消费者则处理数据,可以理解为队列,一端用来放任务,另一端用来处理任务。

    class ThreadPool{private:    int thread_num; //固定线程数    static queue<Task*> task_queue;//任务队列,多个线程共享    static bool shutdown;//用于关闭线程池    static pthread_mutex_t mutex;//互斥锁    static pthread_cond_t condition; //条件变量    pthread_t* pthread_ids;//存储线程IDpublic:    ThreadPool(int);//创建thread_num个线程    static void* threadFunc(void*);//关键,线程通过循环+阻塞+锁去从共享队列中去取任务执行    void addTask(Task*);//添加任务,由于访问共享队列,需要加锁    void joinAll();//所有任务执行完,回收所有线程    ~ThreadPool();};
  • 线程处理任务(threadFunc)
    (1)条件变量的使用需要用到互斥锁,因为判断的条件是共享队列的长度,对于共享资源的访问都需要加锁。
    (2)在唤醒线程到重新加锁中间,共享数据可能发生变化,需要重新判断,所以引入循环判断。

    void* ThreadPool::threadFunc(void* threadData){    while (!shutdown)    {        pthread_mutex_lock(&mutex);        while (task_queue.size() == 0 && !shutdown)        {            pthread_cond_wait(&condition, &mutex);            //先释放锁,然后阻塞            //当收到通知,先唤醒线程,然后重新加锁        }        if (task_queue.size() > 0)        {            Task* task = task_queue.front();            task_queue.pop();            pthread_mutex_unlock(&mutex);            task->run();        }        else pthread_mutex_unlock(&mutex);    }    return (void*)0;}

    源码地址:https://github.com/wedy542700927/ThreadPool
    这是一个简单的线程池实现,如果某个线程做了过多的任务,说明每个线程所分到的时间片不等。是否需要平衡时间片分配,以及如何做线程调度,定时器等也是需要考虑的问题。