当前位置: 首页 > article >正文

Linux相关概念和易错知识点(32)(互斥锁和条件变量的封装、日志和线程池的实现)

目录

1.互斥锁的封装

(1)封装的目的

(2)实现代码

2.条件变量的封装

3.日志的实现

(1)刷新策略

①刷新到控制台

②刷新到文件

(2)构建日志信息

①刷新策略的选择

②生成日志信息

(3)宏观调用

①生成LogMessage临时对象

②宏函数简化操作

(4)全部代码

4.线程池的实现

(1)线程池的初始化

(2)线程启动

(3)激活线程

(4)释放线程池

(4)单例(懒汉)模式改进

(5)全部代码

①myThreadPool.hpp

②myThread.hpp


1.互斥锁的封装

我重点讲讲互斥锁的封装,条件变量的原理一致。

(1)封装的目的

如何封装都是小问题,毕竟互斥锁的相关函数也就那几个,我们重点要理解的就是为什么要封装,它和我们正常调用pthread_mutex系列函数有区别吗?回答了这个问题,我们自然对C++面向对象的特性有了更深的了解。

我们在封装之前,就是全局或局部定义一把锁,然后在需要的时候调用函数来加锁解锁。当我们需要达到某个功能时(如加锁解锁),我们会去直接调用实现这个功能最根本的方法,这就是面向过程设计。就像老师想关心学生成绩,就直接跑到班上去一个一个问。但万一记错了呢?跑错班了呢?这始终不方便。

当封装之后,就形成一个管理一把锁的类。什么叫管理一把锁的类?就是这个类的成员变量里有一把锁,用户不能直接操作这把锁,只能通过对外的接口来操作这把锁。假设定义了myMutex这个类,我需要一把锁,就生成个管理这把锁的结构体,锁就在这个结构体内。当这个线程需要加锁时,只需调用mutex.lock()即可。相当于将该线程的需求发送给了结构体,这个结构体负责实现细节。调用类的成员函数和其它代码的是同一个线程,因此能实现目的。就像老师就设计了一个学生管理系统,老师需要学生成绩就在系统上面点击获取成绩的需求,最后能得到成绩,至于使用细节不是这个老师关心的了。全程老师都面向系统来管理学生,老师和学生之间有一个中间层,这个中间层就是我们常说的面向对象编程的对象。

(2)实现代码

遵循上面的原则,我们可以得到如下代码

#pragma once

#include <iostream>
#include <pthread.h>

using namespace std;

namespace myMutexModule
{
	class myMutex
	{
	public:
		myMutex(const myMutex &) = delete;					// 锁不能被拷贝
		const myMutex &operator=(const myMutex &) = delete; // 锁不能被赋值

		myMutex()
		{
			pthread_mutex_init(&_mutex, nullptr); // 一个互斥锁已被创建并初始化
		}

		void lock()
		{
			pthread_mutex_lock(&_mutex); // 线程已加锁
		}

		void unlock()
		{
			pthread_mutex_unlock(&_mutex); // 线程已释放锁
		}

		pthread_mutex_t *Get_Lock_Ptr()
		{
			return &_mutex;
		}

		~myMutex()
		{
			pthread_mutex_destroy(&_mutex); // 一个互斥锁已被销毁
		}

	private:
		pthread_mutex_t _mutex; // 线程互斥的核心就是管理这把锁,mutex模块只需要描述好这把锁就可以了
	};

	// 当后续调用myLockGuard LockGuard(mutex)时(即创建myLockGuard对象时)会自动上锁,也会自动析构,这就是RAII思想,理解了智能指针一定能看懂这个类
	// 注意不能用匿名对象,因为临时对象的生命周期就一行,也就是说仅在一行内加锁解锁,没意义
	class myLockGuard
	{
	public:
		myLockGuard(myMutex &mymutex)
			: _mymutex(mymutex)
		{
			_mymutex.lock();
		}

		~myLockGuard()
		{
			_mymutex.unlock();
		}

	private:
		myMutex &_mymutex;
	};

}

2.条件变量的封装

实现代码如下,和互斥锁原理一模一样

#pragma once

#include <iostream>
#include <pthread.h>
#include "myMutex.hpp"

using namespace std;

namespace myCondModule
{
	class myCond
	{
	public:
		myCond(const myCond &) = delete; // 这个队列不能拷贝不能赋值
		const myCond &operator=(const myCond &) = delete;

		myCond()
		{
			pthread_cond_init(&_cond, nullptr); // 一个等待队列已被创建并初始化
		}

		void wait(myMutexModule::myMutex &mutex)
		{
			pthread_cond_wait(&_cond, mutex.Get_Lock_Ptr()); // 线程已加入等待队列,并自动释放锁
		}

		void notify()
		{
			pthread_cond_signal(&_cond); // 线程已移出等待队列,获得了锁并已开始向下执行代码
		}

		void notify_all()
		{
			pthread_cond_broadcast(&_cond); // 直接唤醒所有的锁
		}

		~myCond()
		{
			pthread_cond_destroy(&_cond); // 一个等待队列已被销毁
		}

	private:
		pthread_cond_t _cond; // 管理好这个队列就能实现同步,和互斥锁是一个原理的
	};

}

3.日志的实现

(1)刷新策略

打印日志的最后一步是要将日志刷新到文件或者显示器文件里,这部分我们用多态来实现。当后续我们拿着LogStrategy的指针或者引用时,我们调用SyncLog就能实现多态地打印。

①刷新到控制台

刷新到控制台和文件均需访问文件资源,那是临界资源,所以需要加锁来避免打印结果错乱。我这里使用的就是RAII思想的加锁,是基于前面互斥锁的封装来实现的。

②刷新到文件

构造时保证对应的log文件已经创建,default路径和文件名可自己定。

对文件IO的操作

调用说明:当用父类调用纯虚函数时,其实是根据虚函数表直接跑到了子类对象里面来调用函数,所以就算子类有自己的私有成员,也不会出现权限错误。

(2)构建日志信息

①刷新策略的选择

刷新策略只负责打印,其它的就交给构建日志信息的类处理,包括如何选择刷新策略

因此,调用log的不同成员函数就可以实现刷新策略的设置,这里利用了多态使得一个类型可以指向不同类型的对象。

②生成日志信息

实现了刷新策略的选择和打印,接下来就是最重要的日志信息生成。这必然是一个类来实现的,但这个类需要放在哪呢?以及我们还没有解决如何将刷新策略的选择和打印联系起来。

下面是一个关键代码

我们用LogMessage生成了信息之后,当这个对象析构的时候就会直接去调用刷新函数,也就是上面的进行策略选择的指针。

也就是说,我们的思路是用一个临时对象生成日志信息后,利用其析构函数将信息通过myLog的成员变量直接调用打印函数。因此,这个类最理想的位置就是作为myLog的内部类。因为实例化的时候可以直接传*this就能初始化LogMessage,能让LogMessage直接指向外部类的对象。

我们已经将刷新策略的选择和打印联系起来了。在LogMessage的构造函数中,我们就要让它自动生成信息。

很多零星的代码无关紧要,这里重要的是思路,最后我会贴出全部代码

很多时候我们在得到基本打印信息后还要多打印一些信息(如在程序中调用printf),这些信息通过重载来实现。

(3)宏观调用

细节思路我们已知,但宏观上这个日志到底是怎么个调用法我们还不清楚。

①生成LogMessage临时对象

LogMessage临时对象生成时会自动生成日志信息,当对象析构时会自动调用外部类的对象进而调用打印函数。因此调用日志打印就需要一个生成LogMessage的临时对象的函数,这个函数必然是在外部类myLog里。这和我们前面选择将LogMessage放在内部类的原因一致,我们直接传*this就能让LogMessage指向外部类了。

外部类本身就实例化出了一个myLog,因此调用log()可以以仿函数的形式打印日志对象。

至此,我们打印日志的外部代码就应该是log(level, file, line)这一行代码,通过这一行代码仿函数式生成LogMessage临时对象,对象析构时自动调用打印函数,一气呵成。

②宏函数简化操作

file和line最好用宏来替换。因此我们需要设计一些宏函数来快速调用

我们只需要调用LOG(level)就可以打印日志了。并且我们只需要使用CONSOLE_LOG()就能选择往显示器打印还是往文件打印,log是全局定义的,所以我们只需修改策略指针的指向即可。

最后结合LogMessage里面的operator<<,我们可以使用LOG(DEBUG) << "这是线程在执行代码"来打印需要的信息,并且前面附上日志信息。

(4)全部代码

注意需要引用"myMutex.hpp"头文件

#pragma once

#include <iostream>
#include <string>
#include <unistd.h>
#include <fstream>
#include <cstring>
#include <sstream>
#include <time.h>
#include <pthread.h>
#include <memory>
#include <filesystem> //C++的文件操作,c++17的新特性
#include "myMutex.hpp"

using namespace std;

// 日志指标:
// 时间戳、日志等级、日志内容、文件名及其行号(可选)、进程线程相关id信息等(可选)
namespace myLogModule // 写日志采用策略模式,多种刷新策略
{

	// 日志默认保存路径
	const string default_log_path = "./log/";
	const string default_log_name = "log.txt";

	// 日志等级
	enum LogLevel // 普通枚举,可直接用
	{
		DEBUG,	 // 调试信息
		INFO,	 // 正常信息
		WARNING, // 警告
		ERROR,	 // 错误
		FATAL	 // 致命错误
	};

	string Level2String(LogLevel level)
	{
		// 直接枚举转换
		switch (level)
		{
		case LogLevel::DEBUG:
			return "DEBUG";
		case LogLevel::INFO:
			return "INFO";
		case LogLevel::WARNING:
			return "WARNING";
		case LogLevel::ERROR:
			return "ERROR";
		case LogLevel::FATAL:
			return "FATAL";
		default:
			return "NONE";
		}
	}

	// 刷新策略,向不同文件中刷新,也就是说在获取到日志要打印的所有信息后调用这些类里面的方法实现不同打印
	class LogStrategy // 多态,父类
	{
	public:
		virtual void SyncLog(const string &message) = 0; // 纯虚函数,用多态实现不同策略
	};

	// 刷新到控制台
	class ConsoleLogStrategy : public LogStrategy
	{
	public:
		void SyncLog(const string &message)
		{
			myMutexModule::myLockGuard LockGuard(_mutex);
			printf("%s\n", message.c_str()); // 自动换行
		}

	private:
		myMutexModule::myMutex _mutex;
	};

	// 刷新到文件中
	class FileLogStrategy : public LogStrategy
	{
	public:
		FileLogStrategy(string log_path = default_log_path, string log_name = default_log_name)
			: _log_path(log_path), _log_name(log_name)
		{
			// 是否存在文件路径
			if (filesystem::exists(_log_path)) // C++17的管理文件的特性,需要的时候再查就行,不需要全部记下来
				return;

			// 临界资源,有可能多个线程来访问,会一块创建文件,所以要用锁
			myMutexModule::myLockGuard LockGuard(_mutex);
			try
			{
				// 不存在就新建,用try
				filesystem::create_directories(_log_path);
			}
			catch (filesystem::filesystem_error &e) // create_directories抛出异常的类型
			{
				// 用cerr打印,提高易读性
				cerr << e.what() << endl; // 库里抛出的异常可用what()来解读
			}
		}

		// c++文件操作
		void SyncLog(const string &message)
		{
			// 保证一次只有一个线程进入执行
			myMutexModule::myLockGuard LockGuard(_mutex);

			// c++IO流文件操作,以app形式打开
			string log = _log_path + _log_name;
			ofstream fout(log, ios::app);

			// 判断文件是否打开
			if (!fout.is_open())
			{
				printf("文件打开失败\n");
				return;
			}

			// 向文件里面写
			fout << message << endl;

			// 关闭文件
			fout.close();
		}

	private:
		string _log_path;
		string _log_name;

		// 锁,防止重复新建文件夹
		myMutexModule::myMutex _mutex;
	};

	// 构建刷新函数的message,日志消息的形成
	class myLog
	{
	public:
		myLog()
		{
			// 默认控制台策略
			// 这是智能指针实现的多态,父类指针管理子类对象
			_strategy = make_shared<ConsoleLogStrategy>(); // 构建shared_ptr类型
		}

		// 调用父类的函数就可实现多态
		void myConsoleLog()
		{
			// 这是智能指针实现的多态,父类指针管理子类对象
			_strategy = make_shared<ConsoleLogStrategy>(); // 构建shared_ptr类型
		}

		void myFileLog()
		{
			// 这是智能指针实现的多态,父类指针管理子类对象
			_strategy = make_shared<FileLogStrategy>(); // 构建shared_ptr类型,这里无需传参,使用默认路径
		}

		// 日志指标:
		// 具体时间、日志等级、日志内容、文件名及其行号(可选)、进程线程相关id信息等(可选)
		// 一个LogMessage包含所有日志信息
		class LogMessage
		{
		public:
			string GetCurTime()
			{
				time_t time_stamp = time(nullptr); // 返回的是时间戳,但要转为具体时间

				tm cur_time; // 库的一个类型,里面有年月日等信息,可以用时间戳来转换
				//_r表示函数可重入,可多个函数进来获取
				localtime_r(&time_stamp, &cur_time); // 将时间戳转为具体时间

				// 形成完整的时间
				char time_message[1024] = {0};
				snprintf(time_message, sizeof(time_message),
						 "%4d-%02d-%02d %02d:%02d:%02d",
						 cur_time.tm_year + 1900,
						 cur_time.tm_mon + 1,
						 cur_time.tm_mday,
						 cur_time.tm_hour,
						 cur_time.tm_min,
						 cur_time.tm_sec);

				return time_message; // 自动构造
			}

			// 构造函数生成日志信息
			LogMessage(LogLevel level, const string &file_name, uint32_t line, myLog &cur_log)
				: _cur_time(GetCurTime()), _level(level), _pid(getpid()), _file_name(file_name), _line(line), _cur_log(cur_log)
			{
				stringstream ssmessage;
				ssmessage << "[" << _cur_time << "] "			 // 具体时间
						  << "[" << Level2String(_level) << "] " // 日志等级
						  << "[" << _pid << "] "				 // 进程pid
						  << "[" << _file_name << "] "			 // 文件名
						  << "[" << _line << "] - ";			 // 日志所在的代码的行号

				// 获取string
				_message = ssmessage.str();
			}

			// 日志信息还能在基本信息后面田间,因此要支持<<,每层<<后返回值都是LogMessage本身的引用
			template <typename T>
			LogMessage &operator<<(const T &data)
			{
				stringstream ssmessage;
				ssmessage << data;
				_message += ssmessage.str();
				return *this; // 多个<<
			}

			~LogMessage()
			{
				if (_cur_log._strategy)
				{
					// 自动按照指定方式输出日志信息
					_cur_log._strategy->SyncLog(_message);
				}
			}

		private:			   // 管理日志信息的生成
			string _cur_time;  // 具体时间
			LogLevel _level;   // 日志等级
			pid_t _pid;		   // 进程pid
			string _file_name; // 文件名
			uint32_t _line;	   // 日志所在的代码的行号

			string _message; // 上述所有信息总和,完整的一条日志信息

			myLog &_cur_log; // 负责不同的策略进行刷新
		};

		// 用来进行LogMessage的快速初始化
		// 这相当于提供了一种仿函数,用类名+(参数)
		LogMessage operator()(LogLevel level, const string &file_name, uint32_t line)
		{
			// 临时对象,值返回,一行代码结束后可以自动调用它的析构,打印日志信息
			return LogMessage(level, file_name, line, *this); // 这里就拿到了外部类的策略了
		}

	private:
		shared_ptr<LogStrategy> _strategy; // 日志刷新的方案,用顶层父类来管理
	};

	myLog log;

// 利用宏函数实现快速打印
// log(level, __FILE__, __LINE)本质是仿函数,调用的operator()
#define LOG(level) log(level, __FILE__, __LINE__) // 获取调用该代码的行号
												  //__FILE__, __LINE是预处理符,是宏替换,替换成文件名和行号

	// 注意宏定义函数末尾不能加分号
#define CONSOLE_LOG() log.myConsoleLog()
#define FILE_LOG() log.myFileLog()

}

4.线程池的实现

线程池的本质就是生产者消费者模型,我们最终的目标是实现如下线程池调用,用一个结构体来管理里面所有的线程,当添加任务时线程就会去分配执行任务,执行完后就等着新任务的加入。最后停止添加任务后再回收所有线程。

(1)线程池的初始化

<T>指的是任务类型,即上一张图的task_t

这里我使用绑定将this指针给隐藏了,使得myThread能够处理这样的任务。我们需要注意到这些线程即将执行的代码都是同一个,这意味着这个consumer_pop一定不是任务

push_back是将即将生成的线程全部通过myThread管理起来,并将myThread管理起来。这些线程接下来会执行的函数就是void(string)类型的。

(2)线程启动

启动的时候修改_isrunning标识,这个标志着线程池是否启动。当调用这个函数的时候,每个线程都会去开始执行绑定的void(string)的函数,这个函数是任务吗?并不是!

下面是所有函数被激活进入函数的代码:

	// 这里对应生产消费模型的消费场所,各个线程进入这个函数来获取任务
		// 每个线程启动后都会执行这个函数,这个函数的执行是交给myThread实现的
		void consumer_pop(string &name) // 消费
		{
			while (1) // 重复进入,直到任务为空,任务为空后都会到等待队列里面
			{
				T task;
				{
					myMutexModule::myLockGuard LockGuard(_mutex); // 上锁,一次一个线程进入等待队列

					while (isTaskEmpty() && _isrunning)
					{
						_wait_consumer++;
						_consumer_cond.wait(_mutex); // 消费者进入等待队列
						_wait_consumer--;
					}

					// 不在运行状态的话,就退出获取任务的代码
					if (isTaskEmpty() && !_isrunning)
						break;

					// 获取任务,将任务pop
					task = _task_queue.front();
					_task_queue.pop();
				}
				// 在这里锁就释放了,获得任务的线程自己执行下面的代码,把锁还回去给其它线程用

				task(name); // 该线程执行它得到的任务,这个任务所需要的参数就需要线程对应的结构体来维护了
			}
		}

所有线程一开始进来之后,由于里面没有任务且_isrunning标识为true,所以所有的线程都会被陷入等待队列中,_wait_consumer用来统计有多少个正在等待的线程。并且我们需要注意就算线程没有被阻塞在等待队列,它们也一定会在这个函数里面死循环,有且仅有_isrunning为false且任务执行完了才会跳出。这是后面销毁线程池的必要条件。

(3)激活线程

每添加一个任务,就会尝试唤醒线程执行代码,直到所有线程都在执行中。同时如果多个线程同时进入的话,很有可能push出现错误,导致被多添加任务,所以要加锁。加锁与否要充分考虑多线程的情况,就算当前不会以多线程来调用这个函数。

(4)释放线程池

这里将标志位改了之后且所有线程执行完任务后,就会从函数中break,可以紧接着释放线程。

(4)单例(懒汉)模式改进

删掉拷贝和赋值,通过一个static指针来提供对象

只有第一次单例指针没有值时才会创建对象,第二次调用就会直接访问已创建好的单例,不会生成新的对象了。

同时这里的代码不能被多个线程访问,这样就不是单例了,所以要进行加锁处理。加锁后最外层又套了一层if,这是为了防止每次调用这个函数都要加锁,会影响速度。

下面是运行结果的参考

(5)全部代码

①myThreadPool.hpp


// 线程池原理:
// 预创建一个线程池,内部维护多个线程,有任务的时候再叫醒线程
// 线程个数可以控制(浮动线程池),但不宜大量波动,保证线程稳定

// 固定线程池

#include <queue>
#include <vector>
#include <functional>
#include "myThread.hpp"
#include "myLog.hpp"
#include "myMutex.hpp"
#include "myCond.hpp"

using placeholders::_1;

using task_t = function<void(string)>;

using namespace myCondModule;
using namespace myMutexModule;
using namespace myLogModule;
using namespace myThreadModule;

myMutexModule::myMutex _mutex_fun; // 只用来保护函数
int fun(string name)
{
	myMutexModule::myLockGuard LockGuard(_mutex_fun); // 上锁,一次一个线程进入等待队列
	static int count = 0;
	LOG(DEBUG) << "[" << name << "]" << "线程在执行任务" << ",该任务共总执行了" << ++count << "次";
	return 0;
}

namespace myThreadPoolModule
{

	// 线程个数,最好为物理CPU个数 * 核数,即一个8核的CPU运行一个程序推荐线程数为8,部分可超线程的机器可设置16等核数的倍数个线程
	const int default_thread_num = 2;
	// 智能指针,指向管理线程的结构体

	// 指定void()的函数,void fun()
	using thread_t = shared_ptr<myThread>;
	// thread_t是管理即将执行void()函数的线程的结构体指针

	// 模板参数T代表任务类型
	template <typename T>
	// 懒汉模式
	class myThreadPool
	{
		// 拷贝构造、赋值重载禁掉
		myThreadPool(const myThreadPool<T> &mtp) = delete;
		myThreadPool<T> &operator=(const myThreadPool<T> &mtp) = delete;
		// 只允许调用构造

	private:
		bool isTaskEmpty()
		{
			return _task_queue.empty();
		}

		// 这里对应生产消费模型的消费场所,各个线程进入这个函数来获取任务
		// 每个线程启动后都会执行这个函数,这个函数的执行是交给myThread实现的
		void consumer_pop(string &name) // 消费
		{
			while (1) // 重复进入,直到任务为空,任务为空后都会到等待队列里面
			{
				T task;
				{
					myMutexModule::myLockGuard LockGuard(_mutex); // 上锁,一次一个线程进入等待队列

					while (isTaskEmpty() && _isrunning)
					{
						_wait_consumer++;
						_consumer_cond.wait(_mutex); // 消费者进入等待队列
						_wait_consumer--;
					}

					// 不在运行状态的话,就退出获取任务的代码
					if (isTaskEmpty() && !_isrunning)
						break;

					// 获取任务,将任务pop
					task = _task_queue.front();
					_task_queue.pop();
				}
				// 在这里锁就释放了,获得任务的线程自己执行下面的代码,把锁还回去给其它线程用

				task(name); // 该线程执行它得到的任务,这个任务所需要的参数就需要线程对应的结构体来维护了
			}
		}

	public:
		myThreadPool(int thread_num = default_thread_num)
			: _thread_num(thread_num), _isrunning(false), _wait_consumer(0)
		{
			for (int i = 0; i < _thread_num; i++)
			{
				// 利用绑定先传this
				_thread_pool.push_back(make_shared<myThread>(bind(&myThreadPool::consumer_pop, this, _1)));
				// this占位,实际上线程执行的函数类型是void(string)
				LOG(INFO) << "构建线程" << _thread_pool.back()->GetName() << "成功";
			}
		}

		// 静态成员函数,直接调用这个函数可以直接获得myThreadPool<T>*
		// 要保护线程池不被再次调用,多线程调用会出现问题
		static myThreadPool<T> *GetInstance()
		{
			// 如果存在单例了,再次调用就会直接得到这个单例,不会第二次生成
			if (_instance == nullptr) // 多加一层判断,可以保证第二次调用这个函数不会加锁
			{
				myMutexModule::myLockGuard LockGuard(_mutex_instance); // 防止多线程调用
				if (_instance == nullptr)
				{
					LOG(INFO) << "线程池创建准备中";
					_instance = new myThreadPool<T>();
					LOG(INFO) << "已完全加载线程池";
				}
			}

			// 调用这个函数,返回的是单例的指针
			return _instance;
		}

		void AddTask(T &&task)
		{
			// 一次只允许一个线程进入
			myMutexModule::myLockGuard LockGuard(_mutex);
			if (!_isrunning)
				return;
			_task_queue.push(move(task));
			if (_wait_consumer)			 // 有任务就唤醒线程,在工作状态下一直处理任务
				_consumer_cond.notify(); // 线程等待队列没有闲置的线程,就不走这行代码
		}

		void StartAddTask()
		{
			myMutexModule::myLockGuard LockGuard(_mutex); // 避免is_running重复访问
			if (_isrunning)
				return;

			_isrunning = true; // 线程可以开始运行了

			for (auto &thread_ptr : _thread_pool)
			{
				thread_ptr->start(); // 所有线程全部陷入consumer_pop中
				LOG(INFO) << "启动线程" << thread_ptr->GetName() << "成功";
			}
		}

		void join_threads()
		{
			for (auto &thread_ptr : _thread_pool)
			{
				thread_ptr->join();
				LOG(INFO) << "释放线程" << thread_ptr->GetName() << "成功";
			}
		}

		void StopAddTask()
		{
			myMutexModule::myLockGuard LockGuard(_mutex);
			if (_isrunning) // 避免重复访问,加锁
			{
				// 不能直接thread_ptr->cancel(),否则任务状态不确定
				_isrunning = false; // 不能再AddTask
				if (_wait_consumer)
					_consumer_cond.notify_all(); // 唤醒所有线程去执行剩余任务
			}
		}

	private:
		myMutexModule::myMutex _mutex;

		int _thread_num;			   // 线程池的线程数
		queue<T> _task_queue;		   // 放任务
		vector<thread_t> _thread_pool; // 存管理线程的结构体指针

		bool _isrunning; // 线程池是否正在工作状态,不在工作状态的话保存线程但不会执行函数

		myCondModule::myCond _consumer_cond; // 消费者的等待队列
		int _wait_consumer;					 // 正在等待的消费者数量

		static myThreadPool<T> *_instance;			   // 单例对应的指针
		static myMutexModule::myMutex _mutex_instance; // 用来保护单例的锁
	};

	// 类里面出现的静态成员初始化要在类外,不加static
	template <typename T>								   // 变量名前要写对应的类域
	myThreadPool<T> *myThreadPool<T>::_instance = nullptr; // 单例的指针初始化
	template <typename T>
	myMutexModule::myMutex myThreadPool<T>::_mutex_instance; // 只用来保护单例
}

②myThread.hpp

#pragma once

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
#include <functional>

using namespace std;

namespace myThreadModule
{
    static int count = 1;

    enum class STATE // 强枚举类型,使用里面的成员需要写成STATE::RUN,普通枚举可以直接写成RUN,也能识别
    {
        READY,
        RUN,
        FINISH,
        CANCEL,
        DETACH
    };

    class myThread
    {
        using func_t = function<void(string &)>; // 传的函数类型规定为void fun(T parameter)

        static void *execute(void *p) // this指针
        {
            static_cast<myThread *>(p)->_fun(static_cast<myThread *>(p)->_name); // 还原this指针类型,并且使它执行对应对象的函数
            return nullptr;
        }

    public:
        myThread(func_t fun)
            : _tid(-1), _state(STATE::READY), _fun(fun), _joinable(true)
        {
            _name = "myThread-" + to_string(count++);
        }

        bool start()
        {
            if (_state == STATE::READY)
            {
                pthread_create(&_tid, nullptr, execute, (void *)this); // 执行的函数类型和void*(void*)不同,需要转接一次
                _state = STATE::RUN;                                   // 修改线程的状态
                return true;
            }
            printf("%s线程start失败\n", _name.c_str());
            return false;
        }

        bool join()
        {
            if (_joinable)
            {
                if (pthread_join(_tid, nullptr) == 0)
                {
                    _joinable = false; // 已回收资源,不能再join
                    _state = STATE::FINISH;
                    return true;
                }
            }
            printf("%s线程join失败\n", _name.c_str());
            return false;
        }

        bool cancel()
        {
            if (_state == STATE::RUN)
            {
                if (pthread_cancel(_tid) == 0)
                {
                    _state = STATE::CANCEL;
                    return true;
                }
            }
            printf("%s线程cancel失败\n", _name.c_str());
            return false;
        }

        bool detach()
        {
            if (_state == STATE::RUN)
            {
                if (pthread_detach(_tid) == 0)
                {
                    _joinable = false; // detach后就不能join了
                    _state = STATE::DETACH;
                    return true;
                }
            }
            printf("%s线程detach失败\n", _name.c_str());
            return false;
        }

        string GetName()
        {
            return _name;
        }

    private:            // 管理线程,只需要描述好这个线程即可
        pthread_t _tid; // 线程的id
        string _name;   // 线程的名字
        STATE _state;   // 线程的状态
        func_t _fun;    // 线程要执行的函数
        bool _joinable; // 这个线程是否能够被join
    };

}


http://www.kler.cn/a/567209.html

相关文章:

  • 微软AI900认证备考全攻略:开启AI职业进阶之路
  • 网络编程 (UDP 和 TCP 介绍和代码实现) [Java EE]
  • 深度学习的正则化深入探讨
  • 小米SU7预估交付量2.45万台,小米15沦为牺牲品价比百元机让路
  • 一文读懂 KYC:金融、IT 领域的关键应用与实践
  • 《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》
  • 植物大战僵尸金铲铲版 v1.1.6(windows+安卓)
  • 云原生数据库砸了 K8S云自建数据库的饭碗--- CXL内存技术
  • vue图表插件ECharts使用指南
  • ListControl双击实现可编辑
  • virtualbox安装ubuntu,配置静态ip
  • easy云盘笔记
  • 服务器数据恢复—raid5阵列硬盘出现坏道导致上层应用崩溃的数据恢复案例
  • 初始化列表
  • 算法002——复写零
  • 基于51单片机的气体测漏仪proteus仿真
  • 关于流水线的理解
  • GD32F450 使用
  • 告别GitHub连不上!一分钟快速访问方案
  • Eslint 和 Prettier 工具的使用