为什么需要多线程?

最简单的多线程长啥样?

为什么需要线程池,有什么问题?

实现的主要原理是什么?

带着这几个问题,我们依次展开。

1.为什么需要多线程?

大部分程序毕竟都不是计算密集型的,简单的说,正常情况下,以单线程的模式来写对程序员而言是最舒心的。因为所有的代码都是顺序执行,非常容易理解!函数一级一级往下调用,代码一行一行执行。但是,代码的世界里,虽然cpu还好,但是却经常需要用到io资源,或者是其他服务器的网络资源,比如像数据库,如果这个时候因此把进程卡住,不管是客户端还是客户端都对用户体验相当糟糕。当然了,计算密集型的运算就更需要多线程,防止主线程被卡住。

2.最简单的多线程长啥样?

举个最简单的例子,服务器采用阻塞式socket,有一个网络线程负责收发包(IO),然后有一个逻辑主线程负责相应的业务操作,主线程和网络线程之间通过最简单的消息队列进行交换,而这个消息队例明显是两个线程都要访问(轮询消息队列是否为空)到的,所以,我们需要给这个消息队列上锁(std::mutex),即可以解决问题。由于比较简单我们就不需要看这个怎么码了。这种模式虽然简单,但是在合适的岗位上,也是极好的!

3.那为什么需要线程池呢,有什么问题?

还以刚才的服务器举例,如果业务线程逻辑比较复杂,又或者他需要访问数据库或者是其他服务器的资源,读取文件等等呢?当然他可以采用异步的数据库接口,但是采用异步意味着业务代码被碎片化。异步是典型的讨厌他,但是又干不掉他的样子。离题了。回归。这个时候我们需要多个业务线程处理了。多个线程就意味着多一份处理能力!回到上个问题,我们的多线程采用轮询消息队列的方式来交换信息,那么这么多个线程,不断的上锁解锁,光这个成本就够了。这个时候,条件变量就上线了(std::condition_variable)就登场了

4.实现的主要原理是什么?

业务线程不要轮询消息队列了,而所有的业务线程处于等待状态,当有消息再来的时候,再由产生消息的人,在我们示例场景就是网络线程了,随便唤醒一个工人线程即可。看看最关键的代码

      //消费者
void consumer()
{
//第一次上锁
std::unique_lock < std::mutex > lck(mutex_);
while (active_)
{
//如果是活动的,并且任务为空则一直等待
while (active_ && task_.empty())
cv_.wait(lck); //如果已经停止则退出
if(!active_)
break; T *quest = task_.front();
task_.pop(); //从任务队列取出后该解锁(任务队列锁)了
lck.unlock(); //执行任务后释放
proc_(quest); //delete quest; //在proc_已经释放该指针了 //重新上锁
lck.lock();
}
}

  

算了,还是直接贴完整代码,看注释吧

#ifndef _WORKER_POOL_H_
#define _WORKER_POOL_H_ //file: worker_pool.h //#define _CRT_SECURE_NO_WARNINGS
// g++ -g -std=c++11 1.cc -D_GLIBCXX_USE_NANOSLEEP -lpthread */ #include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
//#include <chrono> template<typename T>
class WorkerPool
{
public:
typedef WorkerPool<T> THIS_TYPE;
typedef std::function<void(T*)> WorkerProc;
typedef std::vector< std::thread* > ThreadVec; WorkerPool()
{
active_ = false;
}
virtual ~WorkerPool()
{
for(ThreadVec::iterator it = all_thread_.begin();it != all_thread_.end();++it)
delete *it;
all_thread_.clear();
}
void Start(WorkerProc f,int worker_num=1)
{
active_ = true;
all_thread_.resize(worker_num);
for (int i = 0; i < worker_num;i++ )
{
all_thread_[i] = new std::thread(std::bind(&THIS_TYPE::consumer,this));
}
proc_ = f;
}
//生产者
void Push(T *t)
{
std::unique_lock < std::mutex > lck(mutex_);
task_.push(t);
cv_.notify_one();
} void Stop()
{
//等待所有的任务执行完毕
mutex_.lock();
while (!task_.empty())
{
mutex_.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
cv_.notify_one();
mutex_.lock();
}
mutex_.unlock(); //关闭连接后,等待线程自动退出
active_ = false;
cv_.notify_all();
for(ThreadVec::iterator it = all_thread_.begin();
it != all_thread_.end();++it)
(*it)->join();
}
private:
//消费者
void consumer()
{
//第一次上锁
std::unique_lock < std::mutex > lck(mutex_);
while (active_)
{
//如果是活动的,并且任务为空则一直等待
while (active_ && task_.empty())
cv_.wait(lck); //如果已经停止则退出
if(!active_)
break; T *quest = task_.front();
task_.pop(); //从任务队列取出后该解锁(任务队列锁)了
lck.unlock(); //执行任务后释放
proc_(quest); //delete quest; //在proc_已经释放该指针了 //重新上锁
lck.lock();
}
} std::mutex mutex_;
std::queue<T*> task_;
std::condition_variable cv_;
bool active_;
std::vector< std::thread* > all_thread_;
WorkerProc proc_;
}; #endif

  写一个类继承一下,并写一个工作函数和回调函数处理

#include "worker_pool.h"
#include <iostream> //为了多耗点cpu,计算斐波那契数列吧
static int fibonacci(int a)
{
//ASSERT(a > 0);
if (a == 1 || a == 2)
return 1;
return fibonacci(a-1) + fibonacci(a-2);
} //异步计算任务
struct AsyncCalcQuest
{
AsyncCalcQuest():num(0),result(0)
{}
//计算需要用到的变量
int num;
int result;
}; //为了测试方便,引入全局变量用于标识线程池已将所有计算完成
const int TOTAL_COUNT = 1000000;
int now_count = 0; //继承一下线程池类,在子类处理计算完成的业务,在我们这里,只是打印一下计算结果
class CalcWorkerPool:public WorkerPool<AsyncCalcQuest>
{
public:
CalcWorkerPool(){} virtual ~CalcWorkerPool()
{
} //在工人线程中执行
void DoWork(AsyncCalcQuest *quest)
{
//算了,不算这个了,根本算不出来
quest->result = fibonacci(quest->num);
//quest->result = quest->num*0.618; //并将已完成任务返回到准备回调的列表
std::unique_lock<std::mutex > lck(mutex_callbacks_);
callbacks_.push_back(quest);
} //在主线程执行
void DoCallback()
{
//组回调任务上锁
std::unique_lock<std::mutex > lck(mutex_callbacks_);
while (!callbacks_.empty())
{
auto *quest = callbacks_.back();
{//此处为业务代码打印一下吧
std::cout << quest->num << " " << quest->result << std::endl;
now_count ++;
}
delete quest; //TODO:这里如果采用内存池就更好了
callbacks_.pop_back();
}
} private:
//这里是准备给回调的任务列表
std::vector<AsyncCalcQuest*> callbacks_;
std::mutex mutex_callbacks_;
}; int main()
{
CalcWorkerPool workers; //工厂开工了 8个工人喔
workers.Start(std::bind(&CalcWorkerPool::DoWork,&workers,std::placeholders::_1),8); //开始产生任务了
for (int i=0; i<TOTAL_COUNT; i++)
{
AsyncCalcQuest *quest = new AsyncCalcQuest;
quest->num = i%40+1;
workers.Push(quest);
} while (now_count != TOTAL_COUNT)
{
workers.DoCallback();
} workers.Stop(); return 0;
}

  linux完整项目 https://github.com/linbc/worker_pool.git

使用c++11写个最简跨平台线程池的更多相关文章

  1. 基于C++11的100行实现简单线程池

    基于C++11的100行实现简单线程池 1 线程池原理 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线程池线程都是后台线程.每个线程都使用默认的堆栈大小, ...

  2. 【C++11应用】基于C++11及std::thread实现的线程池

    目录 基于C++11及std::thread实现的线程池 基于C++11及std::thread实现的线程池 线程池源码: #pragma once #include <functional&g ...

  3. c++11 实现半同步半异步线程池

    感受: 随着深入学习,现代c++给我带来越来越多的惊喜- c++真的变强大了. 半同步半异步线程池: 事实上非常好理解.分为三层 同步层:通过IO复用或者其它多线程多进程等不断的将待处理事件加入到队列 ...

  4. c++11之100行实现简单线程池

    代码从github上拷的,写了一些理解,如有错误请指正 Threadpool.h #ifndef THREAD_POOL_H #define THREAD_POOL_H #include <ve ...

  5. 第11章 Windows线程池(2)_Win2008及以上的新线程池

    11.2 Win2008以上的新线程池 (1)传统线程池的优缺点: ①传统Windows线程池调用简单,使用方便(有时只需调用一个API即可) ②这种简单也带来负面问题,如接口过于简单,无法更多去控制 ...

  6. 第11章 Windows线程池(1)_传统的Windows线程池

    第11章 Windows线程池 11.1 传统的Windows线程池及API (1)线程池中的几种底层线程 ①可变数量的长任务线程:WT_EXECUTELONGFUNCTION ②Timer线程:调用 ...

  7. Java多线程之Executor框架和手写简易的线程池

    目录 Java多线程之一线程及其基本使用 Java多线程之二(Synchronized) Java多线程之三volatile与等待通知机制示例 线程池 什么是线程池 线程池一种线程使用模式,线程池会维 ...

  8. Java线程池简聊

    在Java中,已经实现了4中内置的线程池,这四种我不多聊. 大家各种网站论坛都能查得到. 现在说一下这四种线程池的基类: ThreadPoolExecutor在ThreadPoolExecutor中你 ...

  9. 手写线程池,对照学习ThreadPoolExecutor线程池实现原理!

    作者:小傅哥 博客:https://bugstack.cn Github:https://github.com/fuzhengwei/CodeGuide/wiki 沉淀.分享.成长,让自己和他人都能有 ...

随机推荐

  1. android httpclient 上传图片

    需要依赖  httpmime.jar /** * 上传图片 * * @param url * 上传地址 * @param filepath * 图片路径 * @return */ public Str ...

  2. EntityFramework6 版本更变产生的错误

    LINQ to Entities does not recognize the method 'System.Nullable`1[System.Int32] DiffMinutes(System.N ...

  3. Windows命令计算MD5与SHA1/256值

    certutil -hashfile file MD5 certutil -hashfile file SHA1 certutil -hashfile file SHA256 示例如下:

  4. html中 alt 和 title 的区别

    alt 用来给图片来提示的(图片载入失败时以文本形式提示). Title用来给链接文字或普通文字提示的(在鼠标放上去的时候就会提示).

  5. 问题集录06--SpringBoot创建Maven项目

    1. 如下图,打开idea之后,file -> new -> project2. 如下图,在弹出的new project 页面,选择maven -> 勾选Create from ar ...

  6. MySQL触发器基本使用

    文章参考:这里 MySQL中,创建触发器的基本语法: CREATE TRIGGER trigger_name trigger_time trigger_event ON tbl_name FOR EA ...

  7. 第三章 使用java实现面向对象 多态

    第三章 多态 一.编写父子类 1.多态是具有表现多种型生态的能力的特征,同一个实现接口,使用不同的实例而执行不同的操作 2.一个引用类型,使用不同的实例而执行不同操作.(父类引用子类对象) 使用多态的 ...

  8. Map.Entry遍历集合中的元素

    Entry是Map中的一个内部累,map.entrySet()可以得到key和value的视图给你一个比较简单的小事例public static void main(String[] args) { ...

  9. [javaEE] JDBC快速入门

    JDBC:Java Data Base Connectivity java数据库连接 1.组成JDBC的两个包:主要是接口 java.sql javax.sql 2.相应JDBC的数据库实现 在tom ...

  10. [WC2016]挑战NPC

    Sol 这做法我是想不到\(TAT\) 每个筐子拆成三个相互连边 球向三个筐子连边 然后跑一般图最大匹配 这三个筐子间最多有一个匹配 那么显然每个球一定会放在一个筐子里,一定有一个匹配 如果筐子间有匹 ...