muduo网络库剖析——事件循环与线程EventLoopThread接口类

本文主要是介绍muduo网络库剖析——事件循环与线程EventLoopThread接口类,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

muduo网络库剖析——事件循环与线程EventLoopThread接口类

  • 前情
    • 从muduo到my_muduo
  • 概要
    • bind
    • unique_lock< mutex > 与 condition_variable
  • 框架与细节
    • 成员
    • 函数
    • 使用方法
  • 源码
  • 结尾

前情

从muduo到my_muduo

作为一个宏大的、功能健全的muduo库,考虑的肯定是众多情况是否可以高效满足;而作为学习者,我们需要抽取其中的精华进行简要实现,这要求我们足够了解muduo库。

做项目 = 模仿 + 修改,不要担心自己学了也不会写怎么办,重要的是积累,学到了这些方法,如果下次在遇到通用需求的时候你能够回想起之前的解决方法就够了。送上一段话!

在这里插入图片描述

概要

因为EventLoop与Thread是两个独立的类,如果要将两个类产生关联,实现one loop one thread,还需要一个EventLoop和Thread的接口类,即EventLoopThread类,其中会实现EventLoop与Thread的启动与关闭,一个EventLoopThread即对应着一个one loop one thread。

bind

里面用到了bind函数,这里通过学习ZYH665的博客,了解到bind的多种用法。

对于bind函数。bind共可以绑定四种函数,分别为 普通函数,类成员函数,类静态函数,lambda表达式,下面将详细介绍四种函数的使用方法。

首先是普通函数中的无参函数。使用方法如下:
在这里插入图片描述
接着是有参普通函数,对于有参数的函数,可以选择绑定哪些参数,参数的顺序需要顺序连续,若是有的参数不需要绑定,就要使用 std::placeholders::_1 占位标识符。使用方式如下:
在这里插入图片描述
占位符按如下方式变化:

auto ite = std::bind(tes,placeholders::_4, placeholders::_3, placeholders::_4, placeholders::_4);
ite(100,200,300,400);
auto ite2 = std::bind(tes, 100, placeholders::_2, 300, placeholders::_1);
ite2(200, 400);

接着是类成员函数,此时bind至少需要两个参数,第一个参数为类的成员函数(需要通过 &类名::成员函数 的方式填入参数,第二个参数为类对象或者类对象的this指针,对象地址也可。
在这里插入图片描述
bind还可以绑定类静态函数,对于类的静态函数就不用传递第二个参数了,直接通过 &类名::成员函数 的方式填入第一个参数即可。
在这里插入图片描述
除此以外还可以绑定lamdba函数。

在这里插入图片描述

unique_lock< mutex > 与 condition_variable

通过阅读༄yi笑奈何的博客,我更加了解condition_variable的使用方式。

条件变量std::condition_variable的作用是阻塞线程,然后等待通知将其唤醒。我们可以通过某个函数判断是否符合某种条件来决定是阻塞线程等待通知还是唤醒线程,由此实现线程间的同步。所以简单来说condition_variable的作用就两个——等待(wait)、通知(notify)。下面详细介绍一下。

首先是wait函数。wait()的是普通的等待。分为有条件的等待和无条件的等待。

在这里插入图片描述
void wait (unique_lock& lck)会无条件的阻塞当前线程然后等待通知,前提是此时对象lck已经成功获取了锁。等待时会调用lck.unlock()释放锁,使其它线程可以获取锁。一旦得到通知(由其他线程显式地通知),函数就会释放阻塞并调用lck.lock(),使lck保持与调用函数时相同的状态。然后函数返回,注意,最后一次lck.lock()的调用可能会在返回前再次阻塞线程。
使用方法是:

#include <iostream>           
#include <thread>             
#include <mutex>              
#include <condition_variable> 
#include <windows.h>std::condition_variable cv;int value;void read_value() {std::cin >> value;cv.notify_one();
}int main()
{std::cout << "Please, enter an integer (I'll be printing dots): \n";std::thread th(read_value);std::mutex mtx;std::unique_lock<std::mutex> lck(mtx);cv.wait(lck);std::cout << "You entered: " << value << '\n';th.join();return 0;
}

wait()函数因为没有条件判断,因此有时候会产生虚假唤醒,而有条件的等待可以很好的解决这一问题。

void wait (unique_lock& lck, Predicate pred)为有条件的等待,pred是一个可调用的对象或函数,它不接受任何参数,并返回一个可以作为bool计算的值。当pred为false时wait()函数才会使线程等待,在收到其他线程通知时只有当pred返回true时才会被唤醒。我们将上述代码做如下修改,它的输出与上述一样。

使用方式如下:

bool istrue()
{return value != 0;
}int main()
{std::cout << "Please, enter an integer (I'll be printing dots): \n";std::thread th(read_value);std::mutex mtx;std::unique_lock<std::mutex> lck(mtx);cv.wait(lck,istrue);std::cout << "You entered: " << value << '\n';th.join();return 0;
}

框架与细节

成员

在这里插入图片描述
EventLoop成员中有一个loop,以及一个thread,以及thread中函数的回调。再包括用来支持线程安全性的互斥锁与条件变量。

函数

在这里插入图片描述
在这里插入图片描述
首先,肯定是EventLoopThread的构造函数。构造函数对该类中的loop,thread,线程初始化函数回调等成员进行了初始化。函数回调默认的是传入一个空函数,默认的thread名也是空字符串。对于函数回调的传入,主要使用了c++11的绑定器,如果是类中函数,还需要传第二个参数this指针。

在这里插入图片描述
接着是析构函数,其中会将退出状态位置1,且将对应的loop会quit,thread也会进行线程分离join。

在这里插入图片描述
startLoop函数里面主要是启动了thread_,调用thread_的start函数,此时应该与loop_做mapping。此时loop_还为空,loop_的赋值在之后的threadFunc中,通过unique_lock< mutex >与condition_variable的结合,会一直等待loop_被赋值了。最终会返回loop,告诉调用者这个EventLoopThread中的loop是哪个。

在这里插入图片描述
对于threadFunc这个函数,用于处理创建相应loop并执行事件循环。我在剖析时产生了一个问题,就是为什么loop_明明是one loop one thread,一个loop对应一个线程,为什么会产生线程安全的问题的呢?为什么需要用到mutex之类的解决线程安全问题的变量呢?在我的印象中,像webserver中的请求队列,是对多个工作线程开放的,这种是需要处理线程安全问题的。

其实,对于loop_,startLoop的主线程会访问loop_,另外EventLoopThread中的thread在调用threadFunc也会访问loop_。所以,也会存在线程安全问题的。

另外,在创建loop前,还需要对thread进行初始化,使用的ThreadInitCallBack回调函数,这个函数是提供给其他类的一个接口,实现方式在其他类中,这里就不再赘述。

使用方法

源码

//EventLoopThread.h
#pragma once#include "noncopyable.h"
#include "Thread.h"#include <functional>
#include <mutex>
#include <condition_variable>
#include <string>class EventLoop;class EventLoopThread : noncopyable {
public:using ThreadInitCallback = std::function<void(EventLoop*)>; EventLoopThread(const ThreadInitCallback &cb = ThreadInitCallback(), const std::string &name = std::string()); //空函数或者用于初始化某些全局状态的函数~EventLoopThread();EventLoop* startLoop();
private:void threadFunc();EventLoop *loop_;bool exiting_;Thread thread_;std::mutex mutex_;std::condition_variable cond_;ThreadInitCallback callback_;
};
//EventLoopThread.cc
#include "EventLoopThread.h"
#include "EventLoop.h"EventLoopThread::EventLoopThread(const ThreadInitCallback &cb, const std::string &name): loop_(nullptr), exiting_(false), thread_(std::bind(&EventLoopThread::threadFunc, this), name), mutex_(), cond_(), callback_(cb) {}EventLoopThread::~EventLoopThread() {exiting_ = true;if (loop_ != nullptr) {loop_->quit();thread_.join();}
}EventLoop* EventLoopThread::startLoop() {thread_.start(); // 启动底层的新线程EventLoop *loop = nullptr;{std::unique_lock<std::mutex> lock(mutex_);while ( loop_ == nullptr ) {cond_.wait(lock);}loop = loop_;}return loop;
}// 下面这个方法,实在单独的新线程里面运行的
void EventLoopThread::threadFunc() {EventLoop loop; // 创建一个独立的eventloop,和上面的线程是一一对应的,one loop per threadif (callback_) {callback_(&loop);}{std::unique_lock<std::mutex> lock(mutex_);loop_ = &loop;cond_.notify_one();}loop.loop(); // EventLoop loop  => Poller.pollstd::unique_lock<std::mutex> lock(mutex_);loop_ = nullptr;
}

结尾

以上就是事件循环与线程EventLoopThread接口类的相关介绍,以及我在进行项目重写的时候遇到的一些问题,和我自己的一些心得体会。发现写博客真的会记录好多你的成长,而且对于一个好的项目,写博客也是证明你确实有过深度思考,并且在之后面试或者工作时遇到同样的问题能够进行复盘的一种有效的手段。所以,希望uu们也可以像我一样,养成写博客的习惯,逐渐脱离菜鸡队列,向大佬前进!!!加油!!!

也希望我能够完成muduo网络库项目的深度学习与重写,并在功能上能够拓展。也希望在完成这个博客系列之后,能够引导想要学习muduo网络库源码的人,更好地探索这篇美丽繁华的土壤。致敬chenshuo大神!!!

鉴于博主只是一名平平无奇的大三学生,没什么项目经验,所以可能很多东西有所疏漏,如果有大神发现了,还劳烦您在评论区留言,我会努力尝试解决问题!

这篇关于muduo网络库剖析——事件循环与线程EventLoopThread接口类的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/643363

相关文章

好题——hdu2522(小数问题:求1/n的第一个循环节)

好喜欢这题,第一次做小数问题,一开始真心没思路,然后参考了网上的一些资料。 知识点***********************************无限不循环小数即无理数,不能写作两整数之比*****************************(一开始没想到,小学没学好) 此题1/n肯定是一个有限循环小数,了解这些后就能做此题了。 按照除法的机制,用一个函数表示出来就可以了,代码如下

禁止平板,iPad长按弹出默认菜单事件

通过监控按下抬起时间差来禁止弹出事件,把以下代码写在要禁止的页面的页面加载事件里面即可     var date;document.addEventListener('touchstart', event => {date = new Date().getTime();});document.addEventListener('touchend', event => {if (new

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

ASIO网络调试助手之一:简介

多年前,写过几篇《Boost.Asio C++网络编程》的学习文章,一直没机会实践。最近项目中用到了Asio,于是抽空写了个网络调试助手。 开发环境: Win10 Qt5.12.6 + Asio(standalone) + spdlog 支持协议: UDP + TCP Client + TCP Server 独立的Asio(http://www.think-async.com)只包含了头文件,不依

poj 3181 网络流,建图。

题意: 农夫约翰为他的牛准备了F种食物和D种饮料。 每头牛都有各自喜欢的食物和饮料,而每种食物和饮料都只能分配给一头牛。 问最多能有多少头牛可以同时得到喜欢的食物和饮料。 解析: 由于要同时得到喜欢的食物和饮料,所以网络流建图的时候要把牛拆点了。 如下建图: s -> 食物 -> 牛1 -> 牛2 -> 饮料 -> t 所以分配一下点: s  =  0, 牛1= 1~

poj 3068 有流量限制的最小费用网络流

题意: m条有向边连接了n个仓库,每条边都有一定费用。 将两种危险品从0运到n-1,除了起点和终点外,危险品不能放在一起,也不能走相同的路径。 求最小的费用是多少。 解析: 抽象出一个源点s一个汇点t,源点与0相连,费用为0,容量为2。 汇点与n - 1相连,费用为0,容量为2。 每条边之间也相连,费用为每条边的费用,容量为1。 建图完毕之后,求一条流量为2的最小费用流就行了

poj 2112 网络流+二分

题意: k台挤奶机,c头牛,每台挤奶机可以挤m头牛。 现在给出每只牛到挤奶机的距离矩阵,求最小化牛的最大路程。 解析: 最大值最小化,最小值最大化,用二分来做。 先求出两点之间的最短距离。 然后二分匹配牛到挤奶机的最大路程,匹配中的判断是在这个最大路程下,是否牛的数量达到c只。 如何求牛的数量呢,用网络流来做。 从源点到牛引一条容量为1的边,然后挤奶机到汇点引一条容量为m的边

poj3750约瑟夫环,循环队列

Description 有N个小孩围成一圈,给他们从1开始依次编号,现指定从第W个开始报数,报到第S个时,该小孩出列,然后从下一个小孩开始报数,仍是报到S个出列,如此重复下去,直到所有的小孩都出列(总人数不足S个时将循环报数),求小孩出列的顺序。 Input 第一行输入小孩的人数N(N<=64) 接下来每行输入一个小孩的名字(人名不超过15个字符) 最后一行输入W,S (W < N),用

配置InfiniBand (IB) 和 RDMA over Converged Ethernet (RoCE) 网络

配置InfiniBand (IB) 和 RDMA over Converged Ethernet (RoCE) 网络 服务器端配置 在服务器端,你需要确保安装了必要的驱动程序和软件包,并且正确配置了网络接口。 安装 OFED 首先,安装 Open Fabrics Enterprise Distribution (OFED),它包含了 InfiniBand 所需的驱动程序和库。 sudo

【机器学习】高斯网络的基本概念和应用领域

引言 高斯网络(Gaussian Network)通常指的是一个概率图模型,其中所有的随机变量(或节点)都遵循高斯分布 文章目录 引言一、高斯网络(Gaussian Network)1.1 高斯过程(Gaussian Process)1.2 高斯混合模型(Gaussian Mixture Model)1.3 应用1.4 总结 二、高斯网络的应用2.1 机器学习2.2 统计学2.3