为什么需要多线程?

最简单的多线程长啥样?

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

实现的主要原理是什么?

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

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. 关于chart不能自行切换出现的报错现象

    1.echart 页面菜单不能切换,line和bar不能自行切换 页面上报错误   bar has not been reqired 解决办法,加载bar <script type=" ...

  2. ubuntu 64上的GCC如何编译32位程序

    运行命令 gcc -v 显示: Target: x86_64-linux-gnu 所以,我这里的gcc默认生成64位的程序. 如果想编出32位的程序,就要加 -m32选项.可是我尝试了,还是不行. 原 ...

  3. 【Linux】Linux C socket 编程之UDP

    发送方: /* * File: main.c * Author: tianshuai * * Created on 2011年11月29日, 下午10:34 * * 主要实现:发送20个文本消息,然后 ...

  4. 在 Linux 平台下使用 JNI

    引言 Java 的出现给大家开发带来的极大的方便.但是,如果我们有大量原有的经过广泛测试的非 Java 代码,将它们全部用 Java 来重写,恐怕会带来巨大的工作量和长期的测试:如果我们的应用中需要访 ...

  5. Wordpress 忘记密码怎么办?

    最近一段时间很忙,很久没更新自己博客了,结果忘记了密码? 这里提供两种方法解决. 1.  点击忘记密码,会根据你的邮箱发送一封密码重置邮件,如果没配制邮件或是空间没开启支持,那就有点悲剧了,可以用第二 ...

  6. 如何通过 PHP 获取 Azure Active Directory 令牌

    在调用 Azure Rest API 时,如果是属于 Azure Resource Manager 的 API,则需要使用 Azure Active Directory (Azure AD)认证获取令 ...

  7. JS实现队列

    JS实现队列: 队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表.进行插入操作的端称为队尾 ...

  8. shell输入与输出功能

    一.shell输入功能 1. 2. 二.shell输出功能 1.字符界面前景颜色 2.字符界面背景颜色 3.其他输出命令 ①cat 输出文本,将文本的格式也输出 ②tee 既输出,也保存到文件里 ③m ...

  9. 第8章 CSS3中的变形与动画(上)

    变形--旋转 rotate() 旋转rotate()函数通过指定的角度参数使元素相对原点进行旋转.它主要在二维空间内进行操作,设置一个角度值,用来指定旋转的幅度.如果这个值为正值,元素相对原点中心顺时 ...

  10. Js事件监听封装(支持匿名函数)

    先看demo:http://liutian1937.github.io/demo/EventListen.html/*绑定事件与取消绑定*/ var handleHash = {}; var bind ...