参考:
https://blog.csdn.net/caoshangpa/article/details/80374651 //看这篇
https://blog.csdn.net/kid1ing/article/details/74512698//看这篇
https://blog.csdn.net/u011388696/article/details/82555625//参考陈硕的
https://blog.csdn.net/BJUT_bluecat/article/details/85633395
https://www.cnblogs.com/yangang92/p/5485868.html
https://blog.csdn.net/caoshangpa/article/details/80374651
最开始报错:abort()has been called问题 https://blog.csdn.net/qq_32563917/article/details/87608046
为什么要使用线程池?
目前的大多数网络服务器,包括Web服务器、Email服务器以及数据库服务器等都具有一个共同点,就是单位时间内必须处理数目巨大的连接请求,但处理时间却相对较短。
传统多线程方案中我们采用的服务器模型则是一旦接受到请求之后,即创建一个新的线程,由该线程执行任务。任务执行完毕后,线程退出,这就是是“即时创建,即时销毁”的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数极其频繁,那么服务器将处于不停的创建线程,销毁线程的状态。我们将传统方案中的线程执行过程分为三个过程:T1、T2、T3。
T1:线程创建时间
T2:线程执行时间,包括线程的同步等时间
T3:线程销毁时间
那么我们可以看出,线程本身的开销所占的比例为(T1+T3) / (T1+T2+T3)。如果线程执行的时间很短的话,这比开销可能占到20%-50%左右。如果任务执行时间很长的话,这笔开销将是不可忽略的。
除此之外,线程池能够减少创建的线程个数。通常线程池所允许的并发线程是有上界的,如果同时需要并发的线程数超过上界,那么一部分线程将会等待。而传统方案中,如果同时请求数目为2000,那么最坏情况下,系统可能需要产生2000个线程。尽管这不是一个很大的数目,但是也有部分机器可能达不到这种要求。
因此线程池的出现正是着眼于减少线程本身带来的开销。线程池采用预创建的技术,在应用程序启动之后,将立即创建一定数量的线程(N1),放入空闲队列中。这些线程都是处于阻塞(Suspended)状态,不消耗CPU,但占用较小的内存空间。当任务到来后,缓冲池选择一个空闲线程,把任务传入此线程中运行。当N1个线程都在处理任务后,缓冲池自动创建一定数量的新线程,用于处理更多的任务。在任务执行完毕后线程也不退出,而是继续保持在池中等待下一次的任务。当系统比较空闲时,大部分线程都一直处于暂停状态,线程池自动销毁一部分线程,回收系统资源。
基于这种预创建技术,线程池将线程创建和销毁本身所带来的开销分摊到了各个具体的任务上,执行次数越多,每个任务所分担到的线程本身开销则越小,不过我们另外可能需要考虑进去线程之间同步所带来的开销
线程池适合场景
事实上,线程池并不是万能的。它有其特定的使用场合。线程池致力于减少线程本身的开销对应用所产生的影响,这是有前提的,前提就是线程本身开销与线程执行任务相比不可忽略。如果线程本身的开销相对于线程任务执行开销而言是可以忽略不计的,那么此时线程池所带来的好处是不明显的,比如对于FTP服务器以及Telnet服务器,通常传送文件的时间较长,开销较大,那么此时,我们采用线程池未必是理想的方法,我们可以选择“即时创建,即时销毁”的策略。总之线程池通常适合下面的几个场合:(1)单位时间内处理任务频繁而且任务处理时间短(2)对实时性要求较高。如果接受到任务后在创建线程,可能满足不了实时要求,因此必须采用线程池进行预创建。
下面代码仍有错误 (错误在于 线程无法并发执行 即使有很多空闲线程 但是还是只能执行一个任务 貌似是锁太nb了 全锁住了)
首先先创建任务类
//
// Task.hpp
// 线程池2
//
// Created by 蓝猫 on 2019/4/25.
// Copyright © 2019年 蓝猫. All rights reserved.
//
#ifndef Task_hpp
#define Task_hpp
#pragma once
#include <stdio.h>
#include <string>
#include <stdio.h>
#include <thread> // std::this_thread::sleep_for
#include <chrono> // std::chrono::seconds
#include <stdlib.h>
namespace bluecat
{
class Task
{
public:
explicit Task(int i):id(i){}
Task() { };
~Task() {};
void run1()
{
printf("hello%d\n", id);
std::this_thread::sleep_for(std::chrono::seconds(id));
//printf("world%d\n", id);
};
void run2()
{
printf("world%d\n", id);
};
int get(){return id;}
int delay(){return rand()%5;}
void print_over() { printf("---task%d:finished.---\n", id); }
private:
int id;
};
};
#endif /* Task_hpp */
线程类定义
//
// MyThreadPool.hpp
// 线程池2
//
// Created by 蓝猫 on 2019/4/25.
// Copyright © 2019年 蓝猫. All rights reserved.
//
#ifndef MyThreadPool_hpp
#define MyThreadPool_hpp
#include <stdio.h>
#pragma once
#include <mutex>
#include <condition_variable>
#include <vector>
#include <memory>
#include <queue>
#include <thread> // std::this_thread::sleep_for
#include <chrono> // std::chrono::seconds
#include"Task.hpp"
using namespace bluecat;
namespace bluecat
{
class MyThreadPool
{
public:
//using Task = std::function<void(void)>;
MyThreadPool(size_t num);
~MyThreadPool();
//friend static void thread_deal(void *ptr, int index);//线程处理函数 设置成为友元函数 有权访问类的所有成员(友元函数也得是静态函数?)
void thread_deal(int index);//线程处理函数 class内部不需要设置成友元函数
void create();//初始化工作线程
void exit();
void addtask(Task &task);//添加任务
private:
std::mutex mutex_t;//互斥锁
std::condition_variable cond;//条件变量
std::queue<Task> que_task;//任务队列
std::vector<std::thread> workers;//工作线程
size_t num_thread;//线程池线程数量
bool is_stop;//线程池是否结束
};
/*
static void thread_deal(void *ptr,int index) //线程处理函数 类外定义 参数需要一个线程池(不知道为啥 需要设置成静态函数才可以)
{
std::shared_ptr<MyThreadPool> pool((MyThreadPool*)ptr);//类型转换
while (true)//把任务队列中的任务都处理掉
{
std::unique_lock<std::mutex> lk(pool->mutex_t);
while (pool->que_task.empty()&&pool->is_stop==false)//任务队列空了且线程池没有结束 阻塞准备处理任务线程
{
pool->cond.wait(lk);
}
//pool->cond.wait(lk, !pool->que_task.empty());//或者wait的第二个参数是false也会阻塞
if (pool->is_stop) //线程池中止并且任务都处理完了 直接退了
{
pool->exit();
return;
}
Task t = std::move(pool->que_task.front());//取队列第一个元素
pool->que_task.pop();//删掉
printf("此任务由线程%d处理\n",index);
t.run1();//执行线程
std::this_thread::sleep_for(std::chrono::seconds(1));
t.run2();
t.print_over();//任务执行完毕
}
return;
}
*/
};
#endif /* MyThreadPool_hpp */
线程类实现
//
// MyThreadPool.cpp
// 线程池2
//
// Created by 蓝猫 on 2019/4/25.
// Copyright © 2019年 蓝猫. All rights reserved.
//
#include "MyThreadPool.hpp"
//#include "pch.h"
//#include "MyThreadPool.h"
#include <iostream>
#include <stdlib.h>
using namespace bluecat;
MyThreadPool::MyThreadPool(size_t num) :num_thread(num), is_stop(false)//构造函数
{
create();
};
MyThreadPool::~MyThreadPool() //析构函数
{
//先创建一个临界区将线程池状态标记为停止,从而禁止新的线程的加入,最后等待所有执行线程的运行结束,完成销毁(不加锁会有任务进来)
{
std::unique_lock<std::mutex> lk(mutex_t);
is_stop = true;
}
cond.notify_all();//唤醒所有等待线程
for (std::thread &worker : workers)
{
worker.join();
}
};
void MyThreadPool::create()
{
//std::thread t(thread_deal, this);
for (int i = 0; i < num_thread; i++)
{
printf("线程%d创建成功\n",i+1);
workers.push_back(std::thread(&MyThreadPool::thread_deal,this,i));//把this指针传进去
}
}
void MyThreadPool::exit()
{
for (std::thread &worker : workers)
{
worker.join();
}
}
void MyThreadPool::addtask(Task &task)//添加任务
{
bool isempty;//判断当前任务队列是否为空 为空就唤醒线程处理函数
std::unique_lock<std::mutex> lk(mutex_t);
isempty = que_task.empty();
que_task.push(std::move(task));
printf("添加任务%d\n"/*,task.get()*/);
if (isempty) //如果之前是空的 唤醒线程 继续执行线程处理函数
{
cond.notify_one();
}
}
void MyThreadPool::thread_deal(int index) //线程处理函数 类外定义 参数需要一个线程池(不知道为啥 需要设置成静态函数才可以)
{
//std::shared_ptr<MyThreadPool> pool((MyThreadPool*)ptr);//类型转换
while (true)//把任务队列中的任务都处理掉
{
std::unique_lock<std::mutex> lk(mutex_t);
while (que_task.empty()&&!is_stop)//任务队列空了且线程池没有结束 阻塞准备处理任务的线程
{
cond.wait(lk);
}
//pool->cond.wait(lk, !pool->que_task.empty());//或者wait的第二个参数是false也会阻塞
if (is_stop) //线程池中止并且任务都处理完了 直接退了
{
return;
}
Task t = std::move(que_task.front());//取队列第一个元素
que_task.pop();//删掉
//lk.unlock();
printf("此任务由线程%d处理\n", index+1);
//t.run1();//执行线程
//t.run2();
//std::this_thread::sleep_for(std::chrono::seconds(t.delay()));
std::cout<<"worker thread ID:"<<std::this_thread::get_id()<<std::endl;
std::cout << "hello " << t.get() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(t.delay()));
std::cout << "world " << t.get() << std::endl;
t.print_over();//任务执行完毕
//std::this_thread::sleep_for(std::chrono::seconds(1));
}
return;
}
main
//
// main.cpp
// 线程池2
//
// Created by 蓝猫 on 2019/4/25.
// Copyright © 2019年 蓝猫. All rights reserved.
//
#include <iostream> // std::cout, std::endl
#include <vector> // std::vector
#include <string> // std::string
#include <future> // std::future
#include <thread> // std::this_thread::sleep_for
#include <chrono> // std::chrono::seconds
#include <queue>
#include <stdlib.h>
#include "MyThreadPool.hpp"
#include "Task.hpp"
#include "thread_pool.hpp"
/*
void func(int n){
int h=rand()%5;
std::this_thread::sleep_for(std::chrono::seconds(h));
printf("hello from %d\n", n);
//std::this_thread::sleep_for(std::chrono::seconds(n));
}
void test(){
ThreadPool pool(1);
pool.start();
for(int i = 0; i < 10; ++i){
pool.append(std::bind(func, i));
}
}
int main(){
test();
}
*/
void func(int n){
int h=rand()%5;
std::this_thread::sleep_for(std::chrono::seconds(h));
printf("hello from %d\n", n);
//std::this_thread::sleep_for(std::chrono::seconds(n));
}
int main()
{
bluecat::MyThreadPool pool(4);
std::vector<bluecat::Task> tasks;
for (int i = 0; i < 8; i++)
{
tasks.emplace_back(bluecat::Task(i+1));
}
for (auto &x : tasks)
{
pool.addtask(x);
//pool.addtask(std::bind(func, 1));
}
return 0;
}
结果
线程1创建成功
线程2创建成功
线程1等待
线程3创建成功
线程2等待
线程4创建成功
线程3等待
添加任务1
唤醒线程
此任务由线程4处理
hello 1
---task1:finished.---
添加任务2
唤醒线程
此任务由线程1处理
hello 2
---task2:finished.---
线程4等待
添加任务3
唤醒线程
此任务由线程2处理
hello 3
---task3:finished.---
线程1等待
线程3等待
添加任务4
唤醒线程
此任务由线程2处理
hello 4
---task4:finished.---
线程4等待
添加任务5
唤醒线程
此任务由线程2处理
hello 5
---task5:finished.---
添加任务6
唤醒线程
此任务由线程1处理
hello 6
---task6:finished.---
线程2等待
线程3等待
添加任务7
唤醒线程
此任务由线程1处理
hello 7
---task7:finished.---
添加任务8
唤醒线程
此任务由线程4处理
hello 8
---task8:finished.---
线程1等待
线程2等待
Program ended with exit code: 0