五种IO模型、多路转接IO:select,poll,epoll(reactor)(技术

news2024/12/22 16:45:36

之前的系统部分的基础IO:就是冯诺依曼结果中的访问磁盘,用内存作为输入输出缓冲区提高效率

现在我们要说的高级IO(input/output):访问的外设(网络中就是网卡):我们的发送和接收接收其实大部分时间都在等,发送在等发送缓冲区中输入了数据,接收在等接收缓冲区有数据,然后在发生拷贝

IO: input:用户从内核缓冲区(内核缓冲区从外设拿)拿数据,output:外设(显示器)从(用户把数据拿给内核缓冲区)内核拿数据
在这里插入图片描述

所以IO=等+拷贝,如何提高IO效率—>减少等的比重

初识五种IO模型:
在这里插入图片描述

阻塞IO:

在内核将数据准备好之前(网卡把数据拷贝给系统套接字缓冲区之前), 系统调用会一直等待. 所有的套接字, 默认都是阻塞方式.
在这里插入图片描述

非阻塞轮询IO

如果内核还未将数据准备好,不会一直阻塞的等待准备好,而是会返回一个EWOULDBLOCK错误码.告诉进程还为准备好,进程去干一下其他事情,然后又轮询回来问一次,直到数据准备好。把数据拷贝给用户,然后就返回成功,
在这里插入图片描述
实际写代码中如何设置成非阻塞呢:
代用系统调用fcntl+F_GETFL,把进程中的某个文件描述符(文件)的属性取出来
然后再用fcntl+F_SETFL把这个文件描述符的IO属性设置为非阻塞

#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <cstdlib>
#include <cerrno>

// 对指定的fd设置非阻塞
void SetNonBlock(int fd)
{
    int fl = fcntl(fd, F_GETFL);
    if(fl < 0)
    {
        std::cerr << "fcntl error" << std::endl;
        exit(0);
    }
    fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}


int main()
{
    SetNonBlock(0);
    while(true)
    {
        char buffer[1024];
        ssize_t s = read(0, buffer, sizeof(buffer)-1); // sizeof(buffer)-1
        if(s > 0)
        {
            buffer[s] = 0;
            std::cout << "echo# " << buffer << std::endl; 
        }
        else if(s == 0)
        {
            std::cout << "end stdin" << std::endl;
            break;
        }
        else
        {
            // 非阻塞等待, 如果数据没有准备好,返回值会按照出错返回, s == -1
            // 数据没有准备好 vs 真的出错了 : 处理方式一定不是一样的。 s无法区分!
            // 数据没有准备好,算读取错误吗?不算。read,recv以出错的形式告知上层,数据还没有准备好
            if(errno == EWOULDBLOCK)
            {
                std::cout << "OS的底层数据还没有就绪, errno: " << errno << std::endl;
                // 做其他事情了
            }
            else if(errno == EINTR)
            {
                std::cout << "IO interrupted by signal, try again" << std::endl;
            }
            else
            {
                std::cout << "read error!" << std::endl;
                break;
            }
        }
        sleep(1);
    }
}

信号驱动IO(简单了解一下)

内核将数据准备好的时候, 使用SIGIO信号通知应用程序进行IO操作.
在这里插入图片描述

IO多路转接(复用):

虽然从流程图上看起来和阻塞IO类似. 实际上最核心在于IO多路转接能够同时等待多个文件描述符的就绪状态
在这里插入图片描述
实现多路转接的几种系统调用:
select技术:
在这里插入图片描述
用一下个select写一个服务器来具体讲述其中的细节:
在这里插入图片描述
因为把要检测的文件描述符设置进去和有就绪返回回来的参数是同一个(输入输出型参数),所以每检测一次,位图都会变化,所以需要借助第三方数组来记录之前已经设置进去的文件描述符,然后在要知道哪些文件描述符是就绪状态时,要遍历数组,首先数组中有数据,其次和输出的位图参数比较是否就绪才可以确定。把新的文件描述符添加进数组后,如果再次去调用的话,位图输入输出型参数又要遍历数组从新设置

#pragma once

#include <iostream>
#include <string>
#include <sys/select.h>
#include <memory>
#include "Log.hpp"
#include "Socket.hpp"

using namespace Net_Work;

const static int gdefaultport = 8888;
const static int gbacklog = 8;
const static int num = sizeof(fd_set) * 8;

class SelectServer
{
private:
    void HandlerEvent(fd_set &rfds)
    {
        for (int i = 0; i < num; i++)
        {
            if (_rfds_array[i] == nullptr)
                continue;
            // 合法的sockfd
            // 读事件分两类,一类是新连接到来。 一类是新数据到来
            int fd = _rfds_array[i]->GetSockFd();
            if (FD_ISSET(fd, &rfds))
            {
                // 读事件就绪
                if (fd == _listensock->GetSockFd())
                {
                    lg.LogMessage(Info, "get a new link\n");
                    // 获取连接
                    std::string clientip;
                    uint16_t clientport;
                    // 不会阻塞!!,因为select已经检测到了listensock已经就绪了
                    Socket *sock = _listensock->AcceptConnection(&clientip, &clientport);
                    if (!sock)
                    {
                        lg.LogMessage(Error, "accept error\n");
                        continue;
                    }
                    lg.LogMessage(Info, "get a client, client info is# %s:%d, fd: %d\n", clientip.c_str(), clientport, sock->GetSockFd());

                    // 这里已经获取连接成功了,接下来怎么办???
                    // read?write?绝对不能!!!read 底层数据是否就绪时不确定的!谁清楚fd上面是否有读事件呢?select!
                    // 新链接fd到来的时候,要把新的fd, 想办法交给select托管 -- 只需要添加到数组_rfds_array中即可
                    int pos = 0;
                    for (; pos < num; pos++)
                    {
                        if (_rfds_array[pos] == nullptr)
                        {
                            _rfds_array[pos] = sock;
                            lg.LogMessage(Info, "get a new link, fd is : %d\n", sock->GetSockFd());
                            break;
                        }
                    }
                    if (pos == num)
                    {
                        sock->CloseSocket();
                        delete sock;
                        lg.LogMessage(Warning, "server is full...!\n");
                    }
                }
                else
                {
                    // 普通的读事件就绪
                    // 读数据是有问题的
                    // 这一次读取不会被卡住吗?
                    std::string buffer;
                    bool res = _rfds_array[i]->Recv(&buffer, 1024);
                    if (res)
                    {
                        lg.LogMessage(Info, "client say# %s\n", buffer.c_str());
                        buffer += ": 你好呀,少年";
                        _rfds_array[i]->Send(buffer);
                        buffer.clear();
                    }
                    else
                    {
                        lg.LogMessage(Warning, "client quit, maybe close or error, close fd : %d\n", _rfds_array[i]->GetSockFd());
                        _rfds_array[i]->CloseSocket();
                        delete _rfds_array[i];
                        _rfds_array[i] = nullptr;
                    }
                }
            }
        }
    }

public:
    SelectServer(int port = gdefaultport) : _port(port), _listensock(new TcpSocket()), _isrunning(false)
    {
    }
    void InitServer()
    {
        _listensock->BuildListenSocketMethod(_port, gbacklog);
        for (int i = 0; i < num; i++)
        {
            _rfds_array[i] = nullptr;
        }
        _rfds_array[0] = _listensock.get();
    }
    void Loop()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // 我们能不能直接accept新连接呢?不能!所有的fd,都要交给select. listensock上面新连接,相当于读事件,有新连接,就等价于有新数据到来
            // 首先不能直接accept,而是将listensock交给select。因为只有select有资格知道有没有IO事件就绪
            // 故意放在循环内部
            // 遍历数组,1. 找最大的fd 2. 合法的fd添加到rfds集合中
            fd_set rfds;
            FD_ZERO(&rfds);
            int max_fd = _listensock->GetSockFd();
            for (int i = 0; i < num; i++)
            {
                if (_rfds_array[i] == nullptr)
                {
                    continue;
                }
                else
                {
                    int fd = _rfds_array[i]->GetSockFd();
                    FD_SET(fd, &rfds); // 添加所有合法fd到rfds集合中
                    if (max_fd < fd)   // 更新最大fd
                    {
                        max_fd = fd;
                    }
                }
            }
            // 定义时间
            struct timeval timeout = {0, 0};
            // rfds本质是一个输入输出型参数,rfds是在select调用返回的时候,不断被修改,所以,每次都要重置
            PrintDebug();
            int n = select(max_fd + 1, &rfds, nullptr, nullptr, /*&timeout*/ nullptr);
            switch (n)
            {
            case 0:
                lg.LogMessage(Info, "select timeout..., last time: %u.%u\n", timeout.tv_sec, timeout.tv_usec);
                break;
            case -1:
                lg.LogMessage(Error, "select error!!!\n");
                break;
            default:
                // 正常的就绪的fd
                lg.LogMessage(Info, "select success, begin event handler, last time: %u.%u\n", timeout.tv_sec, timeout.tv_usec);
                HandlerEvent(rfds); // _rfds_array: 3,4,5,6,7,8,9,10 -> rfds: 4,5,6
                break;
            }
        }
        _isrunning = false;
    }
    void Stop()
    {
        _isrunning = false;
    }

    void PrintDebug()
    {
        std::cout << "current select rfds list is : ";
        for (int i = 0; i < num; i++)
        {
            if (_rfds_array[i] == nullptr)
                continue;
            else
                std::cout << _rfds_array[i]->GetSockFd() << " ";
        }
        std::cout << std::endl;
    }
    ~SelectServer()
    {
    }

private:
    std::unique_ptr<Socket> _listensock;
    int _port;
    int _isrunning;

    // select 服务器要被正确设计,需要程序员定义数据结构,来把所有的fd管理起来,往往是数组!
    Socket *_rfds_array[num];
};

select的优缺点:
优点:select只负责等待,可以等待多个fd,提高了IO的效率
缺点:输入输出型参数:每次都要对参数从新设置,内核每次都要对参数位图进行遍历,拷贝给用户
需要第三方数组来不断的遍历来设置位图和读取位图
位图是有类型fd_set的,所以最多能检测的文件是有上限的
从内核到用户从用户到内核调用的收拾同一个参数

poll技术:
只是改进:通过数组首元素地址管理fd,解决了能检测的fd的上限问题,因为参数是数组指针,所以不用每次都重新设置一遍,而是把新的文件描述符加到数组中就行,对要检测什么状态是设置在数组的元素值中的结构中的,而且输入输出参数是分离的,所以也不用每次的设置
在这里插入图片描述
但是还是有的和select一样的一些问题没有被解决:
需要对返回的数组遍历才能找到就绪的文件描述符,随着检测fd的数量增加效率会降低,每次虽然不用遍历重置fds,但是fds是改变了的,传参时还是要把从用户拷贝给内核

#pragma once

#include <iostream>
#include <string>
#include <poll.h>
#include <memory>
#include "Log.hpp"
#include "Socket.hpp"

using namespace Net_Work;

const static int gdefaultport = 8888;
const static int gbacklog = 8;
const int gnum = 1024;

class PollServer
{
private:
    void HandlerEvent()
    {
        for (int i = 0; i < _num; i++)
        {
            if (_rfds[i].fd == -1)
                continue;
            // 合法的sockfd
            // 读事件分两类,一类是新连接到来。 一类是新数据到来
            int fd = _rfds[i].fd;
            short revents = _rfds[i].revents;

            if (revents & POLLIN)
            {
                // 新连接到来了
                if (fd == _listensock->GetSockFd())
                {
                    lg.LogMessage(Info, "get a new link\n");
                    // 获取连接
                    std::string clientip;
                    uint16_t clientport;
                    // 不会阻塞!!,因为select已经检测到了listensock已经就绪了
                    int sock = _listensock->AcceptConnection(&clientip, &clientport);
                    if (sock == -1)
                    {
                        lg.LogMessage(Error, "accept error\n");
                        continue;
                    }
                    lg.LogMessage(Info, "get a client, client info is# %s:%d, fd: %d\n", clientip.c_str(), clientport, sock);

                    // 这里已经获取连接成功了,接下来怎么办???
                    // read?write?绝对不能!!!read 底层数据是否就绪时不确定的!谁清楚fd上面是否有读事件呢?poll!
                    // 新链接fd到来的时候,要把新的fd, 想办法交给poll托管 -- 只需要添加到数组_rfds中即可
                    int pos = 0;
                    for (; pos < _num; pos++)
                    {
                        if (_rfds[pos].fd == -1)
                        {
                            _rfds[pos].fd = sock;
                            _rfds[pos].events = POLLIN;
                            lg.LogMessage(Info, "get a new link, fd is : %d\n", sock);
                            break;
                        }
                    }
                    if (pos == _num)
                    {
                        // 1. 扩容
                        // 2. 关闭
                        close(sock);
                        lg.LogMessage(Warning, "server is full...!\n");
                    }
                }
                else
                {
                    // 普通的读事件就绪
                    // 读数据是有问题的
                    // 这一次读取不会被卡住吗?
                    char buffer[1024];
                    ssize_t n = recv(fd, buffer, sizeof(buffer-1), 0); // 这里读取会阻塞吗?不会!
                    if (n > 0)
                    {
                        buffer[n] = 0;
                        lg.LogMessage(Info, "client say# %s\n", buffer);
                        std::string message = "你好呀,少年, ";
                        message += buffer;
                        send(fd, message.c_str(), message.size(), 0);
                    }
                    else
                    {
                        lg.LogMessage(Warning, "client quit, maybe close or error, close fd : %d\n", fd);
                        close(fd);
                        // 取消poll的关心
                        _rfds[i].fd = -1;
                        _rfds[i].events = 0;
                        _rfds[i].revents = 0;
                    }
                }
            }
        }
    }

public:
    PollServer(int port = gdefaultport) : _port(port), _listensock(new TcpSocket()), _isrunning(false), _num(gnum)
    {
    }
    void InitServer()
    {
        _listensock->BuildListenSocketMethod(_port, gbacklog);
        _rfds = new struct pollfd[_num];
        for (int i = 0; i < _num; i++)
        {
            _rfds[i].fd = -1;
            _rfds[i].events = 0;
            _rfds[i].revents = 0;
        }
        // 最开始的时候,只有一个文件描述符, Listensock
        _rfds[0].fd = _listensock->GetSockFd();
        _rfds[0].events |= POLLIN;
    }
    void Loop()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // 定义时间
            int timeout = -1;
          
            PrintDebug();
            int n = poll(_rfds, _num, timeout);
            switch (n)
            {
            case 0:
                lg.LogMessage(Info, "poll timeout...\n");
                break;
            case -1:
                lg.LogMessage(Error, "poll error!!!\n");
                break;
            default:
                // 正常的就绪的fd
                lg.LogMessage(Info, "select success, begin event handler\n");
                HandlerEvent(); // _rfds_array: 3,4,5,6,7,8,9,10 -> rfds: 4,5,6
                break;
            }
        }
        _isrunning = false;
    }
    void Stop()
    {
        _isrunning = false;
    }

    void PrintDebug()
    {
        std::cout << "current poll fd list is : ";
        for (int i = 0; i < _num; i++)
        {
            if (_rfds[i].fd == -1)
                continue;
            else
                std::cout << _rfds[i].fd << " ";
        }
        std::cout << std::endl;
    }
    ~PollServer()
    {
        delete[] _rfds;
    }

private:
    std::unique_ptr<Socket> _listensock;
    int _port;
    int _isrunning;
    struct pollfd *_rfds;
    int _num;
};

对poll的问题:需要对返回的数组遍历才能找到就绪的文件描述符,随着检测fd的数量增加效率会降低,
进行改良的技术:epoll
epoll:效率最高的多路转接技术
在这里插入图片描述

在我们调用epoll_wait从就绪队列中拿到关心的fd和就绪事件的时候,就要对该fd如果是读事件的话,就要去读取,但是一次读取并不能保证能够读取完(可能你设置的接收的字符串只有1024),所以epoll的通知模式有两种
LT模式(epoll默认模式):数据没有被处理完或者没处理就会一直通知,(你这一次没有取完,这个继续队列中就还会保留着这个fd和事件的节点,只到下次下下次…把数据取完了才会释放,也就不会再通知了)。
ET模式:只会通知一次,不再通知,直到这个fd又有这个事件发生才会通知。这样为了不发生数据丢失,所以应用层的程序员就要循环的去把这次通知的fd中的数据都读完。既然是循环,不可避免的最后一次循环一定是没有数据就阻塞的,但是我们epoll是多路转接技术,是不允许IO的时候阻塞的,所以epoll技术的ET模式的IO要用非阻塞IO

LT和ET那个更高效
从两者的特性来看,ET更高效因为ET的每次通知都是有效的
从数据发送速度来看:ET更高效,因为ET的通知特性,所以上层就必须在收到通知的时候把这个fd中收到的数据都读完,这样他的接收缓冲区不就更大了吗,那么他在TCP的报文发送给发送端的窗口大小就更大了,发送端直到他的滑动窗口更大了,发送数据的效率就更高了

reactor:
就是把这个进程关心的事件+fd,和epoll模块封装,然后当epoll中有事件就绪的时候,就进行事件的派发——把事件+fd派发出去

写事件的关心是按需关心的,刚开始是一直写,当写不进去(发送缓冲区满了),才把他的写事件关心起来,如果下次写,可以写完,那么他就又要删除写事件的关心

在这里插入图片描述

异步IO: 由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据).
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

奇奇怪怪的知识又增加了---给数据自动加上千分位

千分位(thousands)&#xff0c;数学领域术语&#xff0c;一种简化数学表达的方式。千分位形式&#xff0c;即从个位数起&#xff0c;每三位之间加一个逗号&#xff0c;例如&#xff0c;将7654321输出成7,654,321。 有粉丝私信问&#xff0c;投稿中经常有编辑要求给数字加上千分…

资产管理系统GLPI的安装配置——Linux(Ubuntu 20.04.6 LTS)

系统版本20.04.6 LTSGLPI版本10.0.16PHP版本7.4.3 1.安装PHP及其依赖。PHP版本必须在7.4.0以上 sudo apt update -y sudo apt upgrade -y sudo apt install -y apache2 php-curl php-zip php-gd php-intl php-intl php-pear php-imagick php-imap php-memcache php-pspell p…

探索腾讯云AI代码助手的效能与实用性

前言开发环境配置项目实例应用一&#xff1a;功能介绍二&#xff1a;项目测试FFmpeg二次开发SDL应用加密播放器 帮助提升建议结语 ​ 前言 腾讯云的AI代码助手是一款强大的编码辅助工具&#xff0c;利用混元代码大模型技术&#xff0c;提供技术对话、代码补全、代码诊断和优化…

MySQL·C/C++访问数据库

目录 准备工作 测试是否安装成功 C/C语言访问 官方文档 接口介绍使用 mysql_init() mysql_close() 补充1&#xff1a;makefile编写 mysql_real_connect() 测试1&#xff1a;编译链接 mysql_query() 测试2&#xff1a;SQL语句测试 改 增 删 查 错误1&#x…

vxe-table树形结构使用setCheckboxRow卡顿--已解决

项目场景&#xff1a; vxe-table树形结构使用setCheckboxRow进行部分节点选中 问题描述 vxe-table树形结构使用setCheckboxRow&#xff0c;在数据较多时卡顿 原因分析&#xff1a; setCheckboxRow内部进行了多次的循环遍历&#xff0c;导致速度慢 解决方案&#xff1a; …

YoloV9改进策略:Block改进|GroupMamba在RepNCSP模块中的革新应用|即插即用

在深度学习和计算机视觉领域&#xff0c;YoloV9以其卓越的性能和高效的检测能力赢得了广泛认可。为了进一步提升YoloV9的性能&#xff0c;我们创新性地引入了GroupMambaLayer作为其RepNCSP模块的核心改进。这一策略不仅显著增强了模型的性能&#xff0c;还优化了参数效率和计算…

[数据集][目标检测]轴承缺陷划痕检测数据集VOC+YOLO格式1166张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;1166 标注数量(xml文件个数)&#xff1a;1166 标注数量(txt文件个数)&#xff1a;1166 标注…

从 Pandas 到 Polars 四十五:Polars、Altair 和 Vegafusion

Altair长期以来一直是我最喜欢的可视化库。它允许我通过简洁且一致的API制作美丽的可视化图表。然而&#xff0c;去年我发现我无法将Polars的DataFrame传递给Altair图表时&#xff0c;我感到很失望。 但那些日子已经过去了。在这篇文章中&#xff0c;我们将探讨随着Altair 5的…

雷达气象学(1)——雷达电磁波的散射

文章目录 1.0 电磁波的特征1.1 散射的概念及类型1.2 散射函数——表示粒子的散射能力1.3 瑞利后向散射函数1.4 后向散射截面——更好地表示粒子的散射能力1.5 反射率因子 1.0 电磁波的特征 雷达的探测方式为电磁波。电磁波是在空间传播的电场和磁场两者结合&#xff0c;它在时…

C++从入门到起飞之——string类的模拟实现 全方位剖析!

&#x1f308;个人主页&#xff1a;秋风起&#xff0c;再归来~&#x1f525;系列专栏&#xff1a;C从入门到起飞 &#x1f516;克心守己&#xff0c;律己则安 目录 1、多文件之间的关系 2、模拟实现常用的构造函数 2.1 无参构造函数 2.2 有参的构造函数 2.3 析构函…

应急响应-主机安全之文件相关命令(Linux操作系统)

目录 概述常用命令file-探测给定文件的类型选项常用选项组合 stat-查看文件信息find-不止查找文件选项测试操作常用选项 locate-比find好用的查找命令选项常用选项组合 chmod-修改文件权限suidsbit chown、chgrp 修改文件所有者/所在组umask-显示或设置创建文件的权限掩码常用选…

理解Spring框架4:事务

理解Spring框架4&#xff1a;事务 (qq.com)

等保密评整改应该申请哪种SSL证书

在等保&#xff08;信息安全等级保护&#xff09;和密评&#xff08;商用密码应用安全性评估&#xff09;整改过程中&#xff0c;申请SSL证书是提升系统安全性的重要环节。下面是等保密评应该申请什么样证书的详细建议 类型选择 1 选择国密或者双算法证书 应优先考虑使用采用…

揭秘新型安卓间谍软件LianSpy的攻击手段

自2021年起&#xff0c;俄罗斯用户已成为一种新型未被记录的安卓后门间谍软件“LianSpy”的攻击目标。 网络安全公司卡巴斯基在2024年3月发现了这款恶意软件&#xff0c;并指出其利用俄罗斯的云服务Yandex Cloud进行命令和控制&#xff08;C2&#xff09;通信&#xff0c;以避免…

2024高中生必备物品有哪些?快收下这份必备物品清单!

随着新学期的脚步临近&#xff0c;为确保学习和生活都能顺利进行&#xff0c;挑选一些实用且高效的好物是非常重要的。在如今的数字化时代下&#xff0c;即使是学生&#xff0c;仍需要一系列智能电子产品&#xff0c;这些产品不仅能够提升学习效率&#xff0c;也能让学生党们的…

声明式UI语法

一、ArkTS的基本组成 Entry // 装饰器 Component // 装饰器 struct Hello { // 自定义组件State myText: string World;build() { // UI描述Column() { // 系统组件Text(Hello ${this.myText}).fontSize(50)Divider()Button(Click me).onClick(() > { // 事件方法t…

一次性讲清AI外呼系统,再也不用人工打电话

相信大家都有了解现在接到的机器人电话越来越多&#xff0c;那么真正操作机器人代替人工打电话其实很简单&#xff0c;学会了自然是节省大量人工拨打电话的时间 为什么电销要用外呼系统|||在现代科技的迅猛发展中&#xff0c;AI机器人已逐渐在各行各业崭露头角&#xff0c;与传…

022_java.lang.ThreadLocal

ThreadLocal使用案例 在并发编程中有时候需要让线程互相协作&#xff0c;而协作可以使用共享数据的方式来实现。针对共享数据的操作就需要锁机制来控制并发行为。锁虽好&#xff0c;但是毕竟会在一定程度上让线程之间互相阻塞。前辈们认为在线程需要互相协作的前提下&#xff…

服务器测试之RAID知识梳理

最近开始整理RAID卡相关规格信息&#xff0c;所以再重新汇总整理一下RAID相关的知识点及细节&#xff0c;尽量写的详细简单使用图示让大家更好理解 1.什么是Raid&#xff1f; RAID 是英文 Redundant Array of Independent Disks 的缩写&#xff0c;中文简称为独立磁盘冗余阵列…

Nuxt3所有页面使用服务端渲染需要注意些什么?

其实服务端渲染很多时候并不是所有页面都需要使用的&#xff0c;但是如果有些项目真的需要所有页面都使用服务端渲染&#xff0c;此时服务器压力很大&#xff0c;那要如何处理更好呢&#xff1f; 一、是否所有页面都需要使用服务端渲染呢&#xff1f; 大家可参考以下这篇文…