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

相关文章

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

揭秘Python Socket网络编程的7种硬核用法

《揭秘PythonSocket网络编程的7种硬核用法》Socket不仅能做聊天室,还能干一大堆硬核操作,这篇文章就带大家看看Python网络编程的7种超实用玩法,感兴趣的小伙伴可以跟随小编一起... 目录1.端口扫描器:探测开放端口2.简易 HTTP 服务器:10 秒搭个网页3.局域网游戏:多人联机对战4.

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

C#如何动态创建Label,及动态label事件

《C#如何动态创建Label,及动态label事件》:本文主要介绍C#如何动态创建Label,及动态label事件,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C#如何动态创建Label,及动态label事件第一点:switch中的生成我们的label事件接着,

Spring Boot3虚拟线程的使用步骤详解

《SpringBoot3虚拟线程的使用步骤详解》虚拟线程是Java19中引入的一个新特性,旨在通过简化线程管理来提升应用程序的并发性能,:本文主要介绍SpringBoot3虚拟线程的使用步骤,... 目录问题根源分析解决方案验证验证实验实验1:未启用keep-alive实验2:启用keep-alive扩展建

SpringBoot使用OkHttp完成高效网络请求详解

《SpringBoot使用OkHttp完成高效网络请求详解》OkHttp是一个高效的HTTP客户端,支持同步和异步请求,且具备自动处理cookie、缓存和连接池等高级功能,下面我们来看看SpringB... 目录一、OkHttp 简介二、在 Spring Boot 中集成 OkHttp三、封装 OkHttp

Linux系统之主机网络配置方式

《Linux系统之主机网络配置方式》:本文主要介绍Linux系统之主机网络配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、查看主机的网络参数1、查看主机名2、查看IP地址3、查看网关4、查看DNS二、配置网卡1、修改网卡配置文件2、nmcli工具【通用

Python循环缓冲区的应用详解

《Python循环缓冲区的应用详解》循环缓冲区是一个线性缓冲区,逻辑上被视为一个循环的结构,本文主要为大家介绍了Python中循环缓冲区的相关应用,有兴趣的小伙伴可以了解一下... 目录什么是循环缓冲区循环缓冲区的结构python中的循环缓冲区实现运行循环缓冲区循环缓冲区的优势应用案例Python中的实现库

使用Python高效获取网络数据的操作指南

《使用Python高效获取网络数据的操作指南》网络爬虫是一种自动化程序,用于访问和提取网站上的数据,Python是进行网络爬虫开发的理想语言,拥有丰富的库和工具,使得编写和维护爬虫变得简单高效,本文将... 目录网络爬虫的基本概念常用库介绍安装库Requests和BeautifulSoup爬虫开发发送请求解

Java终止正在运行的线程的三种方法

《Java终止正在运行的线程的三种方法》停止一个线程意味着在任务处理完任务之前停掉正在做的操作,也就是放弃当前的操作,停止一个线程可以用Thread.stop()方法,但最好不要用它,本文给大家介绍了... 目录前言1. 停止不了的线程2. 判断线程是否停止状态3. 能停止的线程–异常法4. 在沉睡中停止5