
目录
关于系统编程的知识我们已经学完了,最后我们需要利用之前写过的代码实现一个线程池,彻底结束系统编程部分。
线程池是一种多线程处理形式,先将任务添加到队列,创建线程后线程池会自动启动线程处理这些任务。
线程池有以下特点:
设计线程池就需要一个线程类管理线程,之前封装的classThread我们增加了一个Context类,这次我们想只使用一个类就完成线程的管理。
虽然需要重新设计,但是原来的函数有一部分也能使用。
线程类的设计思路:
最终实现的Thread.hpp
- #include
- #include
- #include
- #define NUM 64
-
- class Thread
- {
- typedef std::function<void*(void*)> func_t;
- public:
- //构造函数创建线程
- Thread()
- {
- //对线程进行规范化命名
- char buffer[NUM];
- snprintf(buffer, sizeof(buffer), "thread%d", _threadnum++);
- _name = buffer;
- }
-
- //启动线程
- void start(func_t func, void* args = nullptr)
- {
- //初始化这两个变量
- _args = args;
- _func = func;
- //创建线程,线程的处理函数为start_routine
- int n = pthread_create(&_tid, nullptr, start_routine, (void*)this);//将this指针传递到执行代码中
- //断言创建成功,这里也可以换成打印错误码的代码
- assert(n == 0);
- }
-
- //利用回调函数
- void* callback()
- {
- return _func(_args);
- }
-
- //获取线程的名字
- std::string threadname()
- {
- return std::string(_name);
- }
-
- void join()
- {
- int n = pthread_join(_tid, nullptr);
- assert(n == 0);
- }
- private:
- //处理函数的函数能只有一个确定的参数,所以只能定义为静态。
- static void* start_routine(void* args)//用args传递this指针
- {
- Thread* pt = static_cast
(args); - return pt->callback();
- }
-
- std::string _name;
- pthread_t _tid;
- func_t _func;
- void* _args;
- static int _threadnum;
- };
-
- int Thread::_threadnum = 1;
我们测试该类
test.cc
- #include
- #include
- #include"Thread.hpp"
-
- using namespace std;
-
- void* test(void* args)
- {
- char* p = (char*)args;
- while(1)
- {
- printf("%s\n", p);
- sleep(1);
- }
- }
-
- int main()
- {
- Thread t;
- t.start(test, (void*)"thread running");
- t.join();
- return 0;
- }
线程可以正常运行:

线程池类负责多线程的创建和维护,并且在基于阻塞队列的生产者消费者模型下运行。其中生产者是生成任务的线程,消费者是线程池维护的多个线程,数据结构为阻塞队列。线程池包括阻塞队列和所有消费者。
线程池类的设计思路:


此时我们再根据任务类型在main函数和handler_task函数中设置打印函数显示线程调度就可以了。我们设置主线程每一秒推送一个计算任务,计算任务直接使用之前的类。
CalTask.hpp
- #include
- #include
- #include
- #define MAX_NUM 10
- //计算任务类
- class CalTask
- {
- typedef std::function<int(int,int,char)> func_t;
- public:
- //默认构造
- CalTask()
- {}
- //构造函数
- CalTask(int a, int b, char op, func_t func)
- :_a(a)
- ,_b(b)
- ,_op(op)
- ,_func(func)
- {}
-
- //仿函数
- std::string operator()()
- {
- int result = _func(_a, _b, _op);
- char buffer[64];
- snprintf(buffer, sizeof(buffer), "%d %c %d = %d", _a, _op, _b, result);
- std::string s(buffer);
- return s;
- }
-
- //显示任务
- std::string show_task()
- {
- char buffer[64];
- snprintf(buffer, sizeof(buffer), "%d %c %d = ?", _a, _op, _b);
- std::string s(buffer);
- return s;
- }
- private:
- func_t _func;
- int _a;
- int _b;
- char _op;
- };
Thread.hpp
- #include
- #include
- #include
- #define NUM 64
-
- class Thread
- {
- typedef std::function<void*(void*)> func_t;
- public:
- //构造函数创建线程
- Thread()
- {
- //对线程进行规范化命名
- char buffer[NUM];
- snprintf(buffer, sizeof(buffer), "thread%d", _threadnum++);
- _name = buffer;
- }
-
- //启动线程
- void start(func_t func, void* args = nullptr)
- {
- //初始化这两个变量
- _args = args;
- _func = func;
- //创建线程,线程的处理函数为start_routine
- int n = pthread_create(&_tid, nullptr, start_routine, (void*)this);//将this指针传递到执行代码中
- //断言创建成功,这里也可以换成打印错误码的代码
- assert(n == 0);
- }
-
- //利用回调函数
- void* callback()
- {
- return _func(_args);
- }
-
- //获取线程的名字
- std::string threadname()
- {
- return std::string(_name);
- }
-
- void join()
- {
- int n = pthread_join(_tid, nullptr);
- assert(n == 0);
- }
- private:
- //处理函数的函数能只有一个确定的参数,所以只能定义为静态。
- static void* start_routine(void* args)//用args传递this指针
- {
- Thread* pt = static_cast
(args); - return pt->callback();
- }
-
- std::string _name;
- pthread_t _tid;
- func_t _func;
- void* _args;
- static int _threadnum;
- };
-
- int Thread::_threadnum = 1;
Threadpool.hpp
- #include
- #include
- #include
-
- #define THREAD_NUM 10
-
- //前面加上声明
- template <class T>
- class ThreadPool;
-
- //线程数据类
- template <class T>
- class ThreadData
- {
- public:
- ThreadPool
* _pthreadpool;//线程池的this指针 - std::string _threadname;//线程的名字
- //构造函数
- ThreadData(ThreadPool
* tp, std::string name) - :_pthreadpool(tp)
- ,_threadname(name)
- {}
- };
-
- //线程池
- template <class T>
- class ThreadPool
- {
- public:
- //构造函数
- ThreadPool(int num = THREAD_NUM)
- :_num(num)
- {
- pthread_mutex_init(&_cmutex, nullptr);//初始化消费互斥锁
- pthread_mutex_init(&_pmutex, nullptr);//初始化生产互斥锁
- pthread_cond_init(&_cond, nullptr);//初始化条件变量
- //创建多个线程
- for(size_t i = 0; i < _num; ++i)
- {
- _threads.push_back(new Thread());
- }
- }
-
- //析构函数
- ~ThreadPool()
- {
- pthread_mutex_destroy(&_cmutex);//销毁消费互斥锁
- pthread_mutex_destroy(&_pmutex);//销毁生产互斥锁
- pthread_cond_destroy(&_cond);//销毁条件变量
- //销毁多个线程
- for(size_t i = 0; i < _num; ++i)
- {
- _threads[i]->join();
- delete _threads[i];
- }
- }
-
- //将所有线程启动
- void run()
- {
- for(size_t i = 0; i < _num; ++i)
- {
- //由于线程函数需要使用线程池类内的函数和每一个线程的名字,所以将它们合起来构造一个线程数据类传递给线程操作函数
- ThreadData
* p = new ThreadData(this, _threads[i]->threadname()); - _threads[i]->start(handler_task, (void*)p);//这里也可以设计一个类
- std::string s(p->_threadname);
- s += " start...\n";
- std::cout << s;
- }
- }
-
- //向线程池推送任务
- void push(const T& data)
- {
- pthread_mutex_lock(&_pmutex);
- _task_queue.push(data);
- pthread_cond_signal(&_cond);
- pthread_mutex_unlock(&_pmutex);
- }
-
- //消费线程取任务,加锁解锁已经在消费线程处理函数里进行了,不需要注意线程安全
- T pop()
- {
- T data = _task_queue.front();
- _task_queue.pop();
- return data;
- }
-
- //静态成员函数需要访问的非静态成员接口
- bool isQueueEmpty() {return _task_queue.empty();}//判断任务队列是否为空
- void lockQueue() {pthread_mutex_lock(&_cmutex);}//给任务队列加锁
- void unlockQueue() {pthread_mutex_unlock(&_cmutex);}//给任务队列解锁
- void threadWait() {pthread_cond_wait(&_cond,&_cmutex);}//将线程放入条件变量的等待队列中
-
- private:
- //消费线程的处理函数
- static void* handler_task(void* args)
- {
- ThreadData
* p = (ThreadData*)args; - while(1)
- {
- p->_pthreadpool->lockQueue();
- //如果任务队列为空,消费者进程会被加入到条件变量的阻塞队列中
- while(p->_pthreadpool->isQueueEmpty())
- {
- p->_pthreadpool->threadWait();
- }
- T data = p->_pthreadpool->pop();
- p->_pthreadpool->unlockQueue();
- printf("%s接受了任务%s并处理完成,结果为:%s\n", p->_threadname.c_str(),
- data.show_task().c_str(), data().c_str());
- }
- delete p;
- return nullptr;
- }
-
- int _num;//维护的线程数量
- std::vector
_threads;//管理多个线程对象的容器 - std::queue
_task_queue;//任务队列 - pthread_mutex_t _cmutex;//消费者互斥锁
- pthread_cond_t _cond;//条件变量
- pthread_mutex_t _pmutex;//生成任务时的互斥锁
- };
test.cc
- #include
- #include
- #include"CalTask.hpp"
- #include"Thread.hpp"
- #include"Threadpool.hpp"
-
- using namespace std;
-
- //计算器函数
- const string ops = "+-*/%";
- int calculate(int a, int b, char op)
- {
- int result = 0;
- switch(op)
- {
- case '+':
- result = a + b;
- break;
- case '-':
- result = a - b;
- break;
- case '*':
- result = a * b;
- break;
- case '/':
- {
- if(b == 0)
- cerr << "除数不能为0\n";
- else
- result = a / b;
- }
- break;
- case '%':
- {
- if(b == 0)
- cerr << "取模的数字不能为0\n";
- else
- result = a % b;
- }
- break;
- default:
- break;
- }
- return result;
- }
-
- int main()
- {
- srand((unsigned int)time(nullptr));
- ThreadPool
* tp = new ThreadPool(); - tp->run();
- for(;;)
- {
- //
- int a = rand()%10;
- int b = rand()%10;
- char op = ops[rand()%ops.size()];
- CalTask task(a, b, op, calculate);
- tp->push(task);
- printf("主线程推送任务:%d %c %d = ?\n", a, op, b);
- sleep(1);
- }
- return 0;
- }
运行结果:

我们之前写了一个LockGuard类,我们可以将这个类在task_handler的加锁代码中就可以用起来。
- #include
- class mutex
- {
- public:
- //构造函数
- mutex(pthread_mutex_t* p = nullptr)
- :_pmutx(p)
- {}
- //加锁
- void lock()
- {
- pthread_mutex_lock(_pmutx);
- }
- //解锁
- void unlock()
- {
- pthread_mutex_unlock(_pmutx);
- }
- private:
- pthread_mutex_t* _pmutx;
- };
-
- class LockGuard
- {
- public:
- LockGuard(pthread_mutex_t* lock_p):_mutex(lock_p)
- {
- _mutex.lock();//构造函数内加锁
- }
- ~LockGuard()
- {
- _mutex.unlock();//析构函数内解锁
- }
- private:
- mutex _mutex;
- };
LockGuard构造函数加锁,析构函数解锁。通过{}控制该变量的生命周控制加锁。
由于该函数是由线程执行的,锁是线程池的私有变量,所以也需要增加一个函数获取锁。

最后改造的handler_task代码如下:

代码正常运行。
饿汉模式和懒汉模式都是单例模式的实现方式,具体的介绍可以看这篇博客的第二章:C++特殊类设计及类型转换_聪明的骑士的博客-CSDN博客
现在我们要将线程池改为这两种单例模式。
饿汉模式:不管以后会不会使用单例对象,只要程序一启动,程序就会先创建一个唯一的实例对象然后再执行其他代码。
首先,增加一个静态对象保证单例的唯一性,静态变量需要在根据自己的类型在类外初始化。

然后需要将构造函数设为私有,并且不允许生成拷贝构造和赋值运算符重载。

最后,添加一个静态函数GetInstance()获取单例的地址。

此时,更改一下主线程的代码:
- int main()
- {
- srand((unsigned int)time(nullptr));
- ThreadPool
::GetInstance()->run(); - for(;;)
- {
- int a = rand()%10;
- int b = rand()%10;
- char op = ops[rand()%ops.size()];
- CalTask task(a, b, op, calculate);
- ThreadPool
::GetInstance()->push(task); - printf("主线程推送任务:%d %c %d = ?\n", a, op, b);
- sleep(1);
- }
- return 0;
- }
程序正常运行。
懒汉模式:单例只有在第一次被使用到时才被建立,就像一个懒汉一样,什么事都拖到截止日才干。
首先,在原来饿汉模式的基础上将单例对象改为单例对象指针,并且设置为空。
然后,为了保证单例的建立是线程安全的,还要增加一个C++11提供的锁并在类外初始化(需要包含mutex.h头文件,这里用C++标准提供的锁是因为它操作更方便)。

最后,使用之前的双检查加锁方式重新设计一下获取单例指针的接口。

主线程代码不需要改,直接运行就可以跑起来了。
最后,所有Linux系统编程的知识就讲解完毕了,接下来我们要进行网络编程的学习。