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 thread

    if (callback_) {
        callback_(&loop);
    }

    {
        std::unique_lock<std::mutex> lock(mutex_);
        loop_ = &loop;
        cond_.notify_one();
    }

    loop.loop(); // EventLoop loop  => Poller.poll
    std::unique_lock<std::mutex> lock(mutex_);
    loop_ = nullptr;
}

结尾

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/347497.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

还在纠结怎么选随身WiFi的,看看这个!随身WiFi靠谱榜第一名推荐!哪个随身WiFi最好用

你是不是还在头疼如何挑选一个靠谱好用的随身WiFi呢&#xff1f;市场上的随身WiFi产品五花八门&#xff0c;每次购买随身WiFi都会被坑&#xff0c;差点就失去购买的信心了~别灰心&#xff0c;一篇文章教你如何挑选一个靠谱好用的随身WiFi&#xff01; 一、5大购买原则&#xff…

使用GtkSharp下载zip包过慢问题解决方案

背景 安装GtkSharp这个包准备使用C#进行跨平台窗体应用程序开发&#xff0c;运行时发现其需要从github上下载【https://github.com/GtkSharp/Dependencies/raw/master/gtk-3.24.24.zip】这个依赖包&#xff0c;不知道是被墙了还是咋的&#xff0c;下载超时导致运行失败。 解决…

【java面试】Spring

目录 1. Spring 介绍1.1 Spring 的优点1.2 Spring 的缺点1.3 详细讲解一下核心容器&#xff08;spring context应用上下文) 模块 2. Spring俩大核心概念IOC&#xff0c;Inversion of Control&#xff0c;控制反转AOP(Aspect-OrientedProgramming)&#xff0c;面向切面编程Sprin…

【第十六课】哈希表(acwing-841字符串哈希 / 详解 / 优秀的文章推荐 / c++代码)

目录 思想 代码如下 一些解释 1.基数P的选择 2.unsigned long long类型 可能需要看的文章博客 思想 咳咳&#xff0c;感觉这个刚开始第一遍接触的时候很抽象&#xff0c;&#xff0c;&#xff0c;还好网友们很强&#xff0c;有很通俗的解释办法hh。 字符串的哈希核心思…

3.7V升5V 12V 24V 30V 24V/5A升压恒压芯片-H6922

升压恒压芯片是一种电源管理集成电路&#xff0c;其主要功能是将输入电压提升到稳定的输出电压。以下是升压恒压芯片的一些优点&#xff1a; 稳定输出电压&#xff1a;升压恒压芯片能够确保输出电压维持在一个恒定的水平&#xff0c;不受输入电压波动的影响。这有助于提供稳定的…

《WebKit 技术内幕》学习之六(1): CSS解释器和样式布局

《WebKit 技术内幕》之六&#xff08;1&#xff09;&#xff1a;CSS解释器和样式布局 CSS解释器和规则匹配处于DOM树建立之后&#xff0c;RenderObject树之前&#xff0c;CSS解释器解释后的结果会保存起来&#xff0c;然后RenderObject树基于该结果来进行规范匹配和布局计算。当…

除了Docusaurus,还有哪些工具可以搭建知识库?(非开源的也可以)

在今天的数字化时代&#xff0c;为了更好地管理和共享企业内部的知识&#xff0c;许多公司都开始寻找适合自己的知识库搭建工具。Docusaurus是一个比较有知名度的开源知识库工具&#xff0c;但除了Docusaurus之外&#xff0c;还有其他非开源的工具同样可以搭建出高效的知识库。…

Wireshark中的ARP协议包分析

Wireshark可以跟踪网络协议的通讯过程&#xff0c;本节通过ARP协议&#xff0c;在了解Wireshark使用的基础上&#xff0c;重温ARP协议的通讯过程。 ARP&#xff08;Address Resolution Protocol&#xff09;地址解析协议&#xff0c;是根据IP地址获取物理地址的一个TCP/IP协议。…

Vue-38、Vue中插件使用

1、新建plugins.js文件 2、可以在plugins.js 定义全局过滤器 定义全局指令 定义混入 给vue原型上添加一个方法 export default {install(Vue){console.log("install",Vue);//全局过滤器Vue.filter(mySlice,function (value) {return value.slice(0,4)});//定义全局…

认识数学建模

文章目录 1 什么是数学建模2 数学建模的比赛形式3 参加数学建模的好处4 数学建模的流程5 数学建模成员分工6 数学建模常用软件7 数学建模竞赛7.1 美国大学生数学建模竞赛7.2 MathorCup高校数学建模挑战赛7.3 华中杯大学生数学建模挑战赛7.4 认证杯数学建模网络挑战赛7.5 华东杯…

让抖音引流到微信小程序的三方工具数灵通

抖音作为一款火爆的短视频社交平台&#xff0c;吸引了数亿用户的关注和喜爱。除了观看和制作视频外&#xff0c;抖音还提供了跳转到小程序的功能&#xff0c;让用户可以享受更多功能和乐趣。那么&#xff0c;如何在抖音中跳转到小程序呢&#xff1f;以下是详细解答&#xff1a;…

【类与对象】你真的知道Java中的类和对象吗?

前言 本篇文章主要是深入讲一讲类和对象&#xff0c;包括他们的关系&#xff0c;内存分配&#xff0c;如何使用等等。其实如果对类和对象有过了解的读者应该会看起来更加舒服&#xff0c;我接下来讲的只要理解就好了&#xff0c;不一定说要特意去背啊这种&#xff0c;你可以收…

P4769 [NOI2018] 冒泡排序 洛谷黑题题解附源码

[NOI2018] 冒泡排序 题目背景 请注意&#xff0c;题目中存在 n 0 n0 n0 的数据。 题目描述 最近&#xff0c;小 S 对冒泡排序产生了浓厚的兴趣。为了问题简单&#xff0c;小 S 只研究对 1 1 1 到 n n n 的排列的冒泡排序。 下面是对冒泡排序的算法描述。 输入&#x…

JS高频面试题(上)

1. 介绍JS有哪些内置对象&#xff1f; 数据封装类对象&#xff1a;Object、Array、Boolean、Number、String 其他对象&#xff1a;Function、Arguments、Math、Date、RegExp、Error ES6新增对象&#xff1a;Symbol&#xff08;标识唯一性的ID&#xff09;、Map、Set、Promise…

【Java程序员面试专栏 专业技能篇】MySQL核心面试指引(二):核心机制策略

关于MySQL部分的核心知识进行一网打尽,包括三部分:基础知识考察、核心机制策略、性能优化策略,通过一篇文章串联面试重点,并且帮助加强日常基础知识的理解,全局思维导图如下所示 本篇Blog为第二部分:核心机制策略,子节点表示追问或同级提问 日志机制 关于MySQL的几…

图像处理python基础

array 读取图片 tensor 模型预测 一般过程&#xff1a;读取数据np->tensor->model->result->np->画图 shape确保图像输入输出尺寸正确 读取图片 将在GPU上运行的tensor类型转变成在CPU上运行的np类型 三类计算机视觉任务的输入&#xff1a; 分类&#xff1…

加速应用开发:低代码云SaaS和源码交付模式如何选

随着数字化转型的加速&#xff0c;企业对于快速开发和交付高质量应用的需求也越来越迫切。为了满足这一需求&#xff0c;开发者们开始探索采用低代码平台进行软件开发工作&#xff0c;以加速应用开发过程。 目前&#xff0c;市场上的低代码产品众多&#xff0c;但基本可分为简单…

imgaug库图像增强指南(38):从入门到精通——图像卷积的全面解析

引言 在深度学习和计算机视觉的世界里&#xff0c;数据是模型训练的基石&#xff0c;其质量与数量直接影响着模型的性能。然而&#xff0c;获取大量高质量的标注数据往往需要耗费大量的时间和资源。正因如此&#xff0c;数据增强技术应运而生&#xff0c;成为了解决这一问题的…

Redis——关于它为什么快?使用场景?以及使用方式?为何引入多线程?

目录 1.既然redis那么快&#xff0c;为什么不用它做主数据库&#xff0c;只用它做缓存&#xff1f; 2.Redis 一般在什么场合下使用&#xff1f; 3.redis为什么这么快&#xff1f; 4.Redis为什么要引入了多线程&#xff1f; 1.既然redis那么快&#xff0c;为什么不用它做主数据…

磺化-Cy5-左旋聚乳酸,Sulfo-Cyanine5-PLLA,一种生物相容性良好的生物降解材料

您好&#xff0c;欢迎来到新研之家 文章关键词&#xff1a;磺化-Cy5-左旋聚乳酸&#xff0c;Sulfo-Cyanine5-PLLA&#xff0c;Sulfo-Cyanine5-Poly(L-lactic acid) 一、基本信息 产品简介&#xff1a;Sulfo Cy5 PLLA, also known as sulfonated Cyanine5 L-polylactic acid,…