【Linux】线程池


线程过多会带来调度开销,进⽽影响缓存局部性和整体性能。⽽线程池维护着多个线程,等待着监督管理者分配可并发执⾏的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利⽤,还能防⽌过分调度。

1.基础版线程池

1.1 初始化

我们实现的是 固定线程数量的线程池

这个线程池的实现会用到之前自己实现的线程、互斥锁、条件变量、日志。

cpp 复制代码
//ThreadPool.hpp文件
#include <iostream>
#include <vector>
#include <string>
#include <queue> //任务队列
#include "Thread.hpp"
#include "Mutex.hpp"
#include "Cond.hpp"
#include "MyLog.hpp"

namespace MyThreadPool
{
    using namespace MyThread;
    using namespace MyMutex;
    using namespace MyCond;
    using namespace MyLog;
    class ThreadPool
    {
    public:
        ThreadPool() {}
        ~ThreadPool() {}
    private:
        
    };
}
cpp 复制代码
//Main.cc文件
#include "ThreadPool.hpp"
using namespace MyThreadPool;

int main()
{
    return 0;
}
bash 复制代码
#Makefile
threadpool:Main.cc
	g++ -o $@ $^ -std=c++17 -lpthread

.PHONY:clean
clean:
	rm -f threadpool

首先成员函数要有管理这些线程的容器,就用vector,还要一个变量设置线程池的线程数量,

我们自己实现的线程要传一个任务去初始化(具体看对应的博客)。

所以这里我们首先需要一个任务函数,假设这个任务就是先获取线程名再用日志打印。

cpp 复制代码
    void Handler() // 返回值为void,参数为空
    {
        char name[128];
        pthread_getname_np(pthread_self(), name, sizeof(name)); // 获得线程名
        while (true)
        {
            LOG(LogLevel::DEBUG) << name << "运行中..."; // 用日志打印
            sleep(1);
        }
    }

然后创建多线程的时候以lambda表达式调用这个Hanlder初始化,lambda的捕捉列表捕捉this。

cpp 复制代码
static const int defaultnum = 5;
class ThreadPool
{
public:
    ThreadPool(int num = defaultnum)
        : _num(num)
    {
        for (int i = 0; i < _num; i++)
        {
            _threads.emplace_back([this](){ Handler(); }); // Lambda表达式
        }
    }

    void Handler() // 返回值为void,参数为空
    {
        char name[128];
        pthread_getname_np(pthread_self(), name, sizeof(name)); // 获得线程名
        while (true)
        {
            LOG(LogLevel::DEBUG) << name << "运行中..."; // 用日志打印
            sleep(1);
        }
    }

    ~ThreadPool() {}

private:
    std::vector<Thread> _threads; // 管理线程
    int _num;                     // 线程数量
};

1.2 启动

线程有了之后我们还要启动线程。

cpp 复制代码
    void Start()
    {
        for (auto &thread : _threads)
        {
            thread.Start();
        }
    }

启动线程后,线程就会处理自己的任务,下面是自己实现的Thread的逻辑。

在Main.cc验证一下。

cpp 复制代码
#include "ThreadPool.hpp"
using namespace MyThreadPool;

int main()
{
    Refresh_Log_To_Console(); //开启日志,往显示器打印
    ThreadPool tp;
    tp.Start();

    sleep(100);
    return 0;
}

任务放在任务队列里,任务队列用模板,然后线程获取任务,获取任务的过程要加锁,没有任务的时候在条件变量下等待。

cpp 复制代码
template <typename T>
class ThreadPool
{
public:
    ThreadPool(int num = defaultnum)
        : _num(num)
    {
        for (int i = 0; i < _num; i++)
        {
            _threads.emplace_back([this](){ Handler(); }); // Lambda表达式
        }
    }

    void Handler() // 返回值为void,参数为空
    {
        char name[128];
        pthread_getname_np(pthread_self(), name, sizeof(name)); // 获得线程名
        while (true)
        {
            T t;
            {
                LockGuard lg(&_mutex); // 加锁
                while(_task.empty()) // 是while不是if
                {
                    _cond.Wait(&_mutex);// 队列为空的时候在条件变量下等
                }
                //到这里时队列里肯定有任务
                t = _task.front(); // 获取任务
                _task.pop();
            }
            t(); // 处理任务时不用在临界区内部
        }
    }

    void Start()
    {
        for (auto &thread : _threads)
        {
            thread.Start();
        }
    }

    ~ThreadPool() {}

private:
    std::vector<Thread> _threads; // 管理线程
    int _num;                     // 线程数量
    std::queue<T> _task;          // 任务队列
    Mutex _mutex;                 // 锁
    Cond _cond;                   // 条件变量
};

获取任务的过程要在临界区内部,但是线程处理任务的时候不用在临界区内部处理,因为这个任务已经属于这个线程了。

1.3 退出

线程退出我们要设置一个标记位_isrunning,初始化为false,线程运行时设为true。

cpp 复制代码
    void Stop()
    {
        _isrunning = false;
    }

线程退出时可能处于等待状态,也可能正在处理任务。

如果线程正在处理任务,要把任务处理完才能行,所以当_isrunning为false并且任务队列为空时才能退出。

cpp 复制代码
    void Handler() // 返回值为void,参数为空
    {
        char name[128];
        pthread_getname_np(pthread_self(), name, sizeof(name)); // 获得线程名
        while (true)
        {
            T t;
            {
                LockGuard lg(&_mutex); // 加锁
                while (_task.empty()) 
                {
                    _cond.Wait(&_mutex); // 在条件变量下等
                }

                if (!_isrunning && _task.empty())
                {
                    LOG(LogLevel::INFO) << name << "已退出, 任务队列无数据";
                    break;
                }
                t = _task.front(); // 获取任务
                _task.pop();
            }
            t(); // 处理任务时不用在临界区内部
        }
    }

如果线程在条件变量下等待,我们需要先唤醒所有在等待的线程,这里可以加一个变量记录休眠的线程的数量,有线程在休眠的再唤醒,因为_sleep_num也会被所有线程访问,所以这里可以加个锁保证它的安全。

cpp 复制代码
private:
    void WakeUpAll()
    {
        LockGuard lg(&_mutex);
        if (_sleep_num > 0) // 有线程在休眠
        {
            LOG(LogLevel::DEBUG) << "唤醒所有线程";
            _cond.Broadcast(); // 唤醒所有线程
        }
    }

public:
    void Stop()
    {
        _isrunning = false;
        WakeUpAll();
    }

有可能线程是因为要退出才被唤醒的,并且此时判断队列为空,然后又进入了等待状态,一直醒不过来,就退出不了,所以在条件变量下等待的判断条件还要修改,完整代码如下。

cpp 复制代码
template <typename T>
class ThreadPool
{
private:
    void WakeUpAll()
    {
        LockGuard lg(&_mutex);
        if (_sleep_num > 0) // 有线程在休眠
        {
            LOG(LogLevel::DEBUG) << "唤醒所有线程";
            _cond.Broadcast(); // 唤醒所有线程
        }
    }

public:
    ThreadPool(int num = defaultnum)
        : _num(num),
          _isrunning(false),
          _sleep_num(0)
    {
        for (int i = 0; i < _num; i++)
        {
            _threads.emplace_back([this](){ Handler(); }); // Lambda表达式
        }
    }

    void Handler() // 返回值为void,参数为空
    {
        char name[128];
        pthread_getname_np(pthread_self(), name, sizeof(name)); // 获得线程名
        while (true)
        {
            T t;
            {
                LockGuard lg(&_mutex);              // 加锁
                while (_task.empty() && _isrunning) // 队列不为空并且没被退出
                {
                    _sleep_num++;
                    _cond.Wait(_mutex); // 在条件变量下等
                    _sleep_num--;
                }

                if (!_isrunning && _task.empty())
                {
                    LOG(LogLevel::INFO) << name << "已退出, 任务队列无数据";
                    break;
                }
                t = _task.front(); // 获取任务
                _task.pop();
            }
            // t(); // 处理任务时不用在临界区内部
        }
    }

    void Start()
    {
        if (_isrunning)
            return; // 线程已经启动就不要重复启动
        _isrunning = true;
        for (auto &thread : _threads)
        {
            thread.Start();
        }
    }

    void Stop()
    {
        _isrunning = false;
        WakeUpAll();
    }

    ~ThreadPool() {}

private:
    std::vector<Thread> _threads; // 管理线程
    int _num;                     // 线程数量
    std::queue<T> _task;          // 任务队列
    Mutex _mutex;                 // 锁
    Cond _cond;                   // 条件变量
    bool _isrunning;              // 是否在运行
    int _sleep_num;               // 在休眠的线程的数量
};

验证一下前面的逻辑,因为现在我们还没有任务,所以模板参数就传个int,把任务处理那一句注释掉,不然会报错。

cpp 复制代码
#include "ThreadPool.hpp"
//using namespace MyThreadPool;

int main()
{
    Refresh_Log_To_Console(); //开启日志,往显示器打印
    ThreadPool<int> tp;
    tp.Start();
    sleep(1);
    tp.Stop();
    sleep(1);
    return 0;
}

1.4 等待

线程退出后,主线程要Join线程,所以这里还要一个Join的接口。

cpp 复制代码
    void Join()
    {
        if (_isrunning == true) // 线程还在运行就直接返回
            return;
        for (auto &thread : _threads)
        {
            thread.Join();
        }
    }
cpp 复制代码
//Main.cc文件
#include "ThreadPool.hpp"
using namespace MyThreadPool;

int main()
{
    Refresh_Log_To_Console(); //开启日志,往显示器打印
    ThreadPool<int> tp;
    tp.Start();
    sleep(1);
    tp.Stop();
    tp.Join();
    return 0;
}

1.5 任务队列

我们要给线程池一个接口,把任务入进去,入任务是有条件的,当线程池还在运行的时候才能入,如果线程是都停止了就不要再入任务了;入了一个任务我们需要让线程知道,怎么才叫让线程知道呢?如果线程全在休眠就唤醒一个线程。

cpp 复制代码
private:
    void WakeUpOne()
    {
        LOG(LogLevel::DEBUG) << "唤醒一个线程";
        _cond.Signal();
    }

public:
    bool Equeue(const T &task)
    {
        if (_isrunning)
        {
            LockGuard lg(&_mutex);
            _task.push(task);                  // 往队列里入任务
            if (_threads.size() == _sleep_num) // 线程全在休眠就叫醒一个
                WakeUpOne();
            return true;
        }
        return false;
    }

现在我们需要加上任务,任务有如下两种形式。

cpp 复制代码
//Task.hpp文件
#include <iostream>
#include <string>
#include <functional>
#include "MyLog.hpp"

using namespace MyLog;
// 任务形式2
using Task_2 = std::function<void()>; // 返回值void,参数为空的函数类型
void Flush()
{
    LOG(LogLevel::DEBUG) << "我是一个刷新的任务";
}

// 任务形式1
class Task_1
{
public:
    Task_1(int a, int b) : _a(a), _b(b), _result(0)
    {
    }
    void Excute()
    {
        _result = _a + _b;
    }
    std::string ResultToString()
    {
        return std::to_string(_a) + "+" + std::to_string(_b) + "=" +
               std::to_string(_result);
    }
    std::string DebugToString()
    {
        return std::to_string(_a) + "+" + std::to_string(_b) + "=?";
    }

private:
    int _a;
    int _b;
    int _result;
};

有了任务,主线程就可以用往队列里入任务,可以隔1秒入一个。

cpp 复制代码
#include "ThreadPool.hpp"
#include "Task.hpp"
using namespace MyThreadPool;

int main()
{
    Refresh_Log_To_Console(); // 开启日志,往显示器打印
    ThreadPool<Task_2> tp;
    tp.Start();

    int task_num = 5;
    while (task_num) // 往队列里入5个任务
    {
        tp.Equeue(Flush); 
        task_num--;
        sleep(1);
    }
    tp.Stop();
    tp.Join();
    return 0;
}

到这里我们的线程池基础版就完成了。整体代码如下。

cpp 复制代码
//ThreadPool.hpp文件
#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include "Thread.hpp"
#include "Mutex.hpp"
#include "Cond.hpp"
#include "MyLog.hpp"

namespace MyThreadPool
{
    using namespace MyThread;
    using namespace MyMutex;
    using namespace MyCond;
    using namespace MyLog;

    static const int defaultnum = 5;

    template <typename T>
    class ThreadPool
    {
    private:
        void WakeUpAll()
        {
            LockGuard lg(&_mutex);
            if (_sleep_num > 0) // 有线程在休眠
            {
                LOG(LogLevel::DEBUG) << "唤醒所有线程";
                _cond.Broadcast(); // 唤醒所有线程
            }
        }

        void WakeUpOne()
        {
            LOG(LogLevel::DEBUG) << "唤醒一个线程";
            _cond.Signal();
        }

    public:
        ThreadPool(int num = defaultnum)
            : _num(num),
              _isrunning(false),
              _sleep_num(0)
        {
            for (int i = 0; i < _num; i++)
            {
                _threads.emplace_back([this](){ Handler(); }); // Lambda表达式
            }
        }

        void Handler() // 返回值为void,参数为空
        {
            char name[128];
            pthread_getname_np(pthread_self(), name, sizeof(name)); // 获得线程名
            while (true)
            {
                T t;
                {
                    LockGuard lg(&_mutex);              // 加锁
                    while (_task.empty() && _isrunning) // 队列不为空并且没被退出
                    {
                        _sleep_num++;
                        _cond.Wait(_mutex); // 在条件变量下等
                        _sleep_num--;
                    }

                    if (!_isrunning && _task.empty())
                    {
                        LOG(LogLevel::INFO) << name << "已退出, 任务队列无数据";
                        break;
                    }
                    t = _task.front(); // 获取任务
                    _task.pop();
                }
                t(); // 处理任务时不用在临界区内部
            }
        }

        void Start()
        {
            if (_isrunning)
                return; // 线程已经启动就不要重复启动
            _isrunning = true;
            for (auto &thread : _threads)
            {
                thread.Start();
            }
        }

        void Join()
        {
            if (_isrunning == true)
                return;
            for (auto &thread : _threads)
            {
                thread.Join();
            }
        }

        void Stop()
        {
            _isrunning = false;
            WakeUpAll();
        }

        bool Equeue(const T &task)
        {
            if (_isrunning)
            {
                LockGuard lg(&_mutex);
                _task.push(task);                  // 往队列里入任务
                if (_threads.size() == _sleep_num) // 线程全在休眠就叫醒一个
                    WakeUpOne();
                return true;
            }
            return false;
        }

        ~ThreadPool() {}

    private:
        std::vector<Thread> _threads; // 管理线程
        int _num;                     // 线程数量
        std::queue<T> _task;          // 任务队列
        Mutex _mutex;                 // 锁
        Cond _cond;                   // 条件变量
        bool _isrunning;              // 是否在运行
        int _sleep_num;               // 在休眠的线程的数量
    };
}

2.单例模式

单例模式就是某个类只允许实例化出一个对象,在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中,此时往往要⽤⼀个单例的类来管理这些数据。

单例模式的类我们需要在语法上约束这个类,比如说可以把这个类的构造函数设置成private,或者禁用拷贝、赋值之类的接口,这样就没办法创建对象了。

那没办法创建对象怎么去创建一个对象呢?可以在类内以static的方式创建一个。

1.1 饿汉模式和懒汉模式

创建单例有两种创建方法,一种叫饿汉模式,一种叫懒汉模式。比如说拿吃饭洗碗的场景为例,

  • 饿汉模式就是吃完饭立即去洗碗,这样下次吃饭的时候就可以立即吃
  • 懒汉模式就是吃完饭了先不洗碗,等下次要吃饭之前在洗碗

下面是一个饿汉模式的伪代码。

cpp 复制代码
//饿汉模式
template <typename T>
class Singleton
{
    static T data; // 1

public:
    static T *GetInstance()
    {
        return &data; // 2
    }
};

在1处定义了一个static的T类型的对象,静态成员属于类,不属于对象,当Singleton类被加载到内存的时候对象自然就被创建出来了,因为这个data是私有的,所以提供了一个函数获取这个data,就是在2处。

被static修饰的变量作用域不变,生命周期会变成全局的,全局变量会在进程地址空间的全局数据区开辟空间,进程在加载时,会把自己的代码区、全局数据区在进程加载的时候就直接创建出来,像堆区和栈区是运行期间才创建的,所以饿汉模式下我们一旦把代码编译好加载到内存,这个对象就直接存在了,所以将来我们想用这个变量的时候直接用就可以了。

当这个类特别大的时候,把数据全部加载到内存效率很低。

大多数情况下我们都是选择懒汉模式,伪代码如下。

cpp 复制代码
//懒汉模式
template <typename T>
class Singleton
{
    static T *inst; // 1

public:
    static T *GetInstance()
    {
        if (inst == NULL)
        {
            inst = new T(); // 2
        }
        return inst;
    }
};

我们在1处定义一个static的T类型的对象的指针,我们在调用GetInstance函数的时候,先判断这个指针是否为空,为空就再创建对象,对象一旦创建,这个指针就不为空了,就不会再new对象了。懒汉模式的思想我们其实早就接触过了。

懒汉模式最核心的思想其实就是延时加载,从而优化服务器的启动速度。

1.2 将进程池改为单例模式

我们将进程池改为单例模式的前提是它可以被改,进程池我们不需要创建多的,一个就够了,所以可以改为单例模式。

  1. 构造函数私有化:构造函数还是要有的,因为我们还是要创建一个对象的。
  2. 禁用拷贝构造:不允许别人对线程池进行拷贝、赋值等操作
  3. 设置单例指针,设计获取单例的接口:要调用获取单例的接口,首先要有一个对象,但是要有对象必须要调用单例获取...所以获取单例的接口要设置为static的,就可以不需要类对象就能调用。
cpp 复制代码
#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include "Thread.hpp"
#include "Mutex.hpp"
#include "Cond.hpp"
#include "MyLog.hpp"

namespace MyThreadPool
{
    using namespace MyThread;
    using namespace MyMutex;
    using namespace MyCond;
    using namespace MyLog;

    static const int defaultnum = 5;

    template <typename T>
    class ThreadPool
    {
    private:
        void WakeUpAll()
        {
            LockGuard lg(&_mutex);
            if (_sleep_num > 0) // 有线程在休眠
            {
                LOG(LogLevel::DEBUG) << "唤醒所有线程";
                _cond.Broadcast(); // 唤醒所有线程
            }
        }

        void WakeUpOne()
        {
            LOG(LogLevel::DEBUG) << "唤醒一个线程";
            _cond.Signal();
        }

        void Start() // Start函数最好也设为私有
        {
            if (_isrunning)
                return; // 线程已经启动就不要重复启动
            _isrunning = true;
            for (auto &thread : _threads)
            {
                thread.Start();
            }
        }

        ThreadPool(int num = defaultnum) // 构造函数私有
            : _num(num),
              _isrunning(false),
              _sleep_num(0)
        {
            for (int i = 0; i < _num; i++)
            {
                _threads.emplace_back([this](){ Handler(); }); // Lambda表达式
            }
        }

        // 禁用赋值和拷贝
        ThreadPool(const ThreadPool<T> &) = delete;
        ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;

    public:
        // static的类不能访问类成员变量和函数,但是可以访问被static修饰的
        static ThreadPool<T> *GetInstance()
        {
            if (_inc == nullptr)
            {
                LOG(LogLevel::DEBUG) << "首次使用, 创建对象";
                _inc = new ThreadPool<T>; // 创建对象
                _inc->Start();            // 创建之后就启动
            }
            return _inc;
        }

        void Handler() // 返回值为void,参数为空
        {
            char name[128];
            pthread_getname_np(pthread_self(), name, sizeof(name)); // 获得线程名
            while (true)
            {
                T t;
                {
                    LockGuard lg(&_mutex);              // 加锁
                    while (_task.empty() && _isrunning) // 队列不为空并且没被退出
                    {
                        _sleep_num++;
                        _cond.Wait(_mutex); // 在条件变量下等
                        _sleep_num--;
                    }

                    if (!_isrunning && _task.empty())
                    {
                        LOG(LogLevel::INFO) << name << "已退出, 任务队列无数据";
                        break;
                    }
                    t = _task.front(); // 获取任务
                    _task.pop();
                }
                t(); // 处理任务时不用在临界区内部
            }
        }

        void Join()
        {
            if (_isrunning == true)
                return;
            for (auto &thread : _threads)
            {
                thread.Join();
            }
        }

        void Stop()
        {
            _isrunning = false;
            WakeUpAll();
        }

        bool Equeue(const T &task)
        {
            if (_isrunning)
            {
                LockGuard lg(&_mutex);
                _task.push(task);                  // 往队列里入任务
                if (_threads.size() == _sleep_num) // 线程全在休眠就叫醒一个
                    WakeUpOne();
                return true;
            }
            return false;
        }

        ~ThreadPool() {}

    private:
        std::vector<Thread> _threads; // 管理线程
        int _num;                     // 线程数量
        std::queue<T> _task;          // 任务队列
        Mutex _mutex;                 // 锁
        Cond _cond;                   // 条件变量
        bool _isrunning;              // 是否在运行
        int _sleep_num;               // 在休眠的线程的数量

        static ThreadPool<T> *_inc; // 单例指针
    };

    // static成员在类外初始化
    template <typename T>
    ThreadPool<T> *ThreadPool<T>::_inc = nullptr;
}

使用方式如下,只能调用GetInstance函数,而且只有首次使用时会创建。

cpp 复制代码
#include "ThreadPool.hpp"
#include "Task.hpp"

using namespace MyThreadPool;

int main()
{
    Refresh_Log_To_Console(); // 开启日志,往显示器打印
    int task_num = 5;
    while (task_num) // 往队列里入10个任务
    {
        ThreadPool<Task_2>::GetInstance()->Equeue(Flush); // 创建单例
        task_num--;
        sleep(1);
    }
    ThreadPool<Task_2>::GetInstance()->Stop(); 
    ThreadPool<Task_2>::GetInstance()->Join();
    return 0;
}

线程池现在是单例模式,而且此时是一个生产者多个消费者,但是如果线程池本身或被多个线程获取呢?也就是如果是多生产者多消费者怎么办呢?此时的单例获取并不是线程安全的,所以我们要加所。

但是这把锁不是之前的锁_mutex,我们还需要再定义一把锁用来保护这个单例,这把锁也要是static的,因为之前的锁_mutex是类内的成员属性,static的函数不能访问内类成员,还有一个原因是在我们创建单例的逻辑里,此时还不存在对象呢,那么此时对象里的锁_mutex也就不存在。

cpp 复制代码
namespace MyThreadPool
{
    //...

    template <typename T>
    class ThreadPool
    {
    private:
        //...

    public:
        // static的类不能访问类成员变量和函数,但是可以访问被static修饰的
        static ThreadPool<T> *GetInstance()
        {
            LockGuard lg(&_lock); // 保护单例
            if (_inc == nullptr)
            {
                LOG(LogLevel::DEBUG) << "首次使用, 创建对象";
                _inc = new ThreadPool<T>; // 创建对象
                _inc->Start();            // 创建之后就启动
            }

            LOG(LogLevel::DEBUG) << "获取线程池单例";
            return _inc;
        }


        //...

    private:
        std::vector<Thread> _threads; // 管理线程
        int _num;                     // 线程数量
        std::queue<T> _task;          // 任务队列
        Mutex _mutex;                 // 锁
        Cond _cond;                   // 条件变量
        bool _isrunning;              // 是否在运行
        int _sleep_num;               // 在休眠的线程的数量

        static ThreadPool<T> *_inc; // 单例指针
        static Mutex _lock;         // 保护单例的锁
    };

    // static成员在类外初始化
    template <typename T>
    ThreadPool<T> *ThreadPool<T>::_inc = nullptr;

    template <typename T>
    Mutex ThreadPool<T>::_lock;
}

但是呢,这个单例就只获取一次,而线程却要每次都在这里申请锁还要等,效率太低,所以我们再加一个判断。

cpp 复制代码
namespace MyThreadPool
{
    //...

    template <typename T>
    class ThreadPool
    {
    private:
        //...

    public:
        // static的类不能访问类成员变量和函数,但是可以访问被static修饰的
        static ThreadPool<T> *GetInstance()
        {
            if (_inc == nullptr) // 双重判断
            {
                LockGuard lg(&_lock); // 保护单例
                if (_inc == nullptr)
                {
                    LOG(LogLevel::DEBUG) << "首次使用, 创建对象";
                    _inc = new ThreadPool<T>; // 创建对象
                    _inc->Start();            // 创建之后就启动
                }
                LOG(LogLevel::DEBUG) << "获取线程池单例";
            }
            return _inc;
        }


        //...

    private:
        //...

        static ThreadPool<T> *_inc; // 单例指针
        static Mutex _lock;         // 保护单例的锁
    };

    // static成员在类外初始化
    template <typename T>
    ThreadPool<T> *ThreadPool<T>::_inc = nullptr;

    template <typename T>
    Mutex ThreadPool<T>::_lock;
}

3.死锁问题

死锁是指在⼀组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占⽤不会
释放的资源⽽处于的⼀种永久等待状态。

申请⼀把锁是原⼦的,但是申请两把锁就不⼀定了。
假如现在线程A和B分别持有锁1和锁2,就会出现如下情况。

如上这种互相申请对方的锁而不释放自己的锁的情况就是死锁。

死锁产生的4个必要条件:

  1. 互斥条件:⼀个资源每次只能被⼀个执⾏流使⽤,就是用锁了,不用锁不就不会产生死锁了嘛。
  2. 请求与保持条件:⼀个执⾏流因请求资源⽽阻塞时,对已获得的资源保持不放
  3. 不剥夺条件:⼀个执⾏流已获得的资源,在末使⽤完之前,不能强⾏剥夺,比如一般情况下A申请的锁A自己释放,剥夺的就是A申请的锁B给释放了,然后B去申请锁。
  4. 循环等待条件:若⼲执⾏流之间形成⼀种头尾相接的循环等待资源的关系,破环这个条件就可以让申请锁1的才可以申请锁2这种解决方法。

避免死锁:破环这4个条件的任意一个

本篇分享就到这里,我们下篇见~

相关推荐
阿拉-M833 小时前
IntelliJ IDEA Windows 系统高频快捷键使用手册
java·windows·intellij-idea
Fcy6483 小时前
C++ vector容器的解析和使用
开发语言·c++·vector
lingggggaaaa4 小时前
小迪安全v2023学习笔记(一百三十四讲)—— Windows权限提升篇&数据库篇&MySQL&MSSQL&Oracle&自动化项目
java·数据库·windows·笔记·学习·安全·网络安全
无限进步_4 小时前
C语言文件操作全面解析:从基础概念到高级应用
c语言·开发语言·c++·后端·visual studio
_OP_CHEN4 小时前
C++基础:(十五)queue的深度解析和模拟实现
开发语言·c++·stl·bfs·queue·容器适配器·queue模拟实现
起床气2334 小时前
C++海战棋开发日记(序)
开发语言·c++
迦蓝叶4 小时前
JAiRouter v1.0.0 正式发布:企业级 AI 服务网关的开源解决方案
java·运维·人工智能·网关·spring·ai·开源
安卓开发者4 小时前
鸿蒙NEXT应用接入快捷栏:一键直达,提升用户体验
java·harmonyos·ux