【IO多路转接】select编程模型

news2024/11/26 9:45:19

文章目录

  • 1 :peach:五种IO模型:peach:
    • 1.1 :apple:阻塞IO:apple:
    • 1.2 :apple:非阻塞IO:apple:
    • 1.3 :apple:信号驱动IO:apple:
    • 1.4 :apple:IO多路转接:apple:
    • 1.5 :apple:异步IO:apple:
    • 1.6 :apple:同步通信&异步通信:apple:
    • 1.7 :apple:阻塞&非阻塞:apple:
    • 1.8 :apple:总结:apple:
    • 1.9 :apple:其他高级IO:apple:
  • 2 :peach:非阻塞IO:peach:
  • 3 :peach:I/O多路转接之select:peach:
    • 3.1 :apple:select函数原型:apple:
    • 3.2 :apple:第一版本的SelectServer:apple:
    • 3.3 :apple:第二版本的SelectServer:apple:
    • 3.3 :apple:socket就绪条件:apple:
      • 3.3.1 :lemon:读就绪:lemon:
      • 3.3.2 :lemon:写就绪:lemon:
    • 3.4 :apple:select缺点:apple:


1 🍑五种IO模型🍑

1.1 🍎阻塞IO🍎

在内核将数据准备好之前,系统调用会一直等待。所有的套接字, 默认都是阻塞方式。
在这里插入图片描述

1.2 🍎非阻塞IO🍎

如果内核还未将数据准备好,系统调用仍然会直接返回,并且返回EWOULDBLOCK错误码。非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为轮询。这对CPU来说是较大的浪费, 一般只有特定场景下才使用。

在这里插入图片描述

1.3 🍎信号驱动IO🍎

内核将数据准备好的时候, 使用SIGIO信号通知应用程序进行IO操作。(注意在这里的拷贝是应用程序的拷贝而不是内核的拷贝)
在这里插入图片描述

1.4 🍎IO多路转接🍎

虽然从流程图上看起来和阻塞IO类似,但实际上最核心在于IO多路转接能够同时等待多个文件描述符的就绪状态。
在这里插入图片描述

1.5 🍎异步IO🍎

由内核在数据拷贝完成时, 通知应用程序。(而信号驱动是告诉应用程序何时可以开始拷贝数据)

在这里插入图片描述

1.6 🍎同步通信&异步通信🍎

  • 所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回. 但是一旦调用返回,就得到返回值了; 换句话说,就是由调用者主动等待这个调用的结果;
  • 异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果; 换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果; 而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。

另外, 我们回忆在讲多进程多线程的时候, 也提到同步和互斥。但这里的同步通信和进程之间的同步是完全不想干的概念。

1.7 🍎阻塞&非阻塞🍎

阻塞和非阻塞关注的是程序在等待调用结果(如消息,返回值)时的状态。

  • 阻塞调用是指调用结果返回之前,当前线程会被挂起,调用线程只有在得到结果之后才会返回;
  • 非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

1.8 🍎总结🍎

任何IO过程中, 都包含两个步骤:第一是等待,第二是拷贝。而且在实际的应用场景中, 等待消耗的时间往往都远远高于拷贝的时间。让IO更高效, 最核心的办法就是让等待的时间尽量少。

1.9 🍎其他高级IO🍎

非阻塞IO,纪录锁,系统V流机制,I/O多路转接(也叫I/O多路复用),readvwritev函数以及存储映射IO(mmap),这些统称为高级IO。
我们此处重点讨论的是I/O多路转接。


2 🍑非阻塞IO🍑

我们知道,一个文件描述符默认是阻塞IO的。
那我们应该怎样设置为非阻塞的呢?
可以使用fcntl函数:

SYNOPSIS
       #include <unistd.h>
       #include <fcntl.h>

       int fcntl(int fd, int cmd, ... /* arg */ );

传入的cmd的值不同, 后面追加的参数也不相同.
fcntl函数有5种功能:

  • 复制一个现有的描述符(cmd=F_DUPFD)
  • 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)
  • 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL)
  • 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)
  • 获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW)

我们此处只是用第三种功能,获取/设置文件状态标记, 就可以将一个文件描述符设置为非阻塞。

void SetNoBlock(int fd)
{
    int fc=fcntl(fd, F_GETFL);
    if(fc<0)
    {
        std::cout<<"fcntl fail errno:"<<errno<<strerror(errno)<<std::endl;
        return;
    }
    fcntl(fd, F_SETFL, fc|O_NONBLOCK);
}
  • 使用F_GETFL将当前的文件描述符的属性取出来(这是一个位图)
  • 然后再使用F_SETFL将文件描述符设置回去. 设置回去的同时, 加上一个O_NONBLOCK参数

案例:轮询方式读取标准输入

void SetNoBlock(int fd)
{
    int fc=fcntl(fd, F_GETFL);
    if(fc<0)
    {
        std::cout<<"fcntl fail errno:"<<errno<<strerror(errno)<<std::endl;
        return;
    }
    fcntl(fd, F_SETFL, fc|O_NONBLOCK);
}

int main()
{
    SetNoBlock(0);
    while(true)
    {
        printf(">>>");
        fflush(stdout);
        char buffer[100];
        int n=read(0, buffer, sizeof(buffer)-1);
        if(n<0)
        {
            if(errno == EAGAIN)
            {
                std::cout<<"please try agagin"<<std::endl;
                sleep(1);
                continue;
            }
            else
            {
                std::cout << "read fail errno:" << errno << " " << strerror(errno) << std::endl;
                return 2;
            }
        }
        else if(n==0)
        {
            std::cout<<"read file end"<<std::endl;
        }
        else
        {
            buffer[n]=0;
            std::cout<<buffer;
        }
    }
    return 0;
}

代码中注意点:
当我们使用非阻塞方式进行读取时,由于我们没有输入数据所以read调用返回值会小于0(以出错形式返回),所以当返回值小于0时还得判断是否是真正的错误。
在这里插入图片描述
验证:
在这里插入图片描述


3 🍑I/O多路转接之select🍑

系统提供select函数来实现多路复用输入/输出模型:

  • select系统调用是用来让我们的程序监视多个文件描述符的状态变化的;
  • 程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变.

3.1 🍎select函数原型🍎

/* According to earlier standards */
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>

       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);



参数解释:

  • 参数nfds是需要监视的最大的文件描述符值+1;
  • readfds,writefds,exceptfds分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集合及异常文件描述符的集合;(均是输入输出型参数)
  • 参数timeout为结构timeval,用来设置select()的等待时间;

我们先来理解下这三个输入输出型参数的作用,拿readfds为例,用户假定要让操作系统帮助我们关心文件描述符3,6,8,9就绪状态,当select函数返回后,就会将已经就绪的文件描述符通过readfds参数传递出来,比如此时只有文件描述符3和8就绪,此时在readfds结构中对应的比特位就会被置1,而没有就绪的事件在位图中就会被置为0(6和9对应的比特位就被置为0)。

参数timeout取值:

  • NULL:则表示select()没有timeout,select将一直被阻塞,直到某个文件描述符上发生了事件;
  • 0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生;
  • 特定的时间值:如果在指定的时间段里没有事件发生,select将超时返回

fd_set结构:
在这里插入图片描述在这里插入图片描述
其实这个结构就是一个整数数组, 更严格的说, 是一个 "位图"结构,使用位图中对应的位来表示要监视的文件描述符。
我们可以计算下它的大小:
在这里插入图片描述
OS提供了一组操作fd_set的接口, 来比较方便的操作位图:

 void FD_CLR(int fd, fd_set *set); // 清除描述词组set中相关fd的位
 int FD_ISSET(int fd, fd_set *set); // 测试描述词组set中相关fd的位是否为真
 void FD_SET(int fd, fd_set *set); // 设置描述词组set中相关fd的位
 void FD_ZERO(fd_set *set); // 清除描述词组set的全部位

timeval结构:
timeval结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。

select函数返回值:

  • 执行成功则返回文件描述词状态已改变的个数;
  • 如果返回0代表在描述词状态改变前已超过timeout时间,没有返回;
  • 当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds, exceptfds和timeout的值变成不可预测;

3.2 🍎第一版本的SelectServer🍎

有了上面的认识后我们来简单的编写下select服务器:
大家先看看下面的写法有什么问题?
在这里插入图片描述

  • 问题1:根据输入输出型参数的特点我们可以知道,当select函数返回时会将已经准备就绪的文件描述符设置进rfds中,同时将没有准备就绪的文件描述符从rfds清除,但是用户想要继续关心之前关心的文件描述符应该怎么办呢?
  • 问题2:当已经有n个连接到来了,此时我们能够直接Accept吗?很明显是不能够的。因为此时我们要区分文件描述符是不是_listensock,是的话用_listensock进行accept,否则的话我们使用该文件描述符进行数据IO。

有了上面的问题,我们可以选择合适的解决方式:比如我们可以创建fd数组来记录我们想要内核帮助关心的文件描述符,每次select前都用fd数组数组初始化一下rfds:

const uint16_t g_port = 8899;
const int N = sizeof(fd_set) * 8;
const int default_fd = -1;
class SelectServer
{
private:
    Sock _listensock;
    uint16_t _port;
    int _fdarr[N];

public:
    SelectServer(const uint16_t port = g_port)
        : _port(port)
    {
    }

    ~SelectServer()
    {
        _listensock.Close();
    }

    void init()
    {
        _listensock.Socket();
        _listensock.Bind(_port);
        _listensock.Listen();

        for (int i = 0; i < N; ++i)
            _fdarr[i] = default_fd;
    }

    void run()
    {

        _fdarr[0] = _listensock.Fd();
        while (true)
        {
            fd_set rfds;
            FD_ZERO(&rfds);
            int max_fd = _fdarr[0];
            for (int i = 0; i < N; ++i)
            {
                if (_fdarr[i] != default_fd)
                    FD_SET(_fdarr[i], &rfds);
                max_fd = max(max_fd, _fdarr[i]);
            }
            // struct timeval timeout = {2, 0};
            int n = select(max_fd + 1, &rfds, nullptr, nullptr, nullptr);
            if (n > 0)
            {
                cout << "有一个就绪事件发生了" << endl;
                // 表示已经有n个连接到来了,此时我们能够直接accept吗?
                hand_event(rfds);
                printf_fd();
            }
            else if (n == 0)
            {
                cout << "time out" << endl;
            }
            else
            {
                cout << "select errno:" << errno << ":" << strerror(errno) << endl;
            }
        }
    }

private:
    void accepter()
    {
        string clientip;
        uint16_t clientport;
        int sock = _listensock.Accept(&clientip, &clientport);
        cout << "[ip:port]:" << clientip << ":" << clientport << endl;
        int pos = 1;
        while (pos < N)
        {
            if (_fdarr[pos] == default_fd)
            {
                _fdarr[pos] = sock;
                break;
            }
            ++pos;
        }
        if (pos > N)
        {
            cout << "_fdarr full" << endl;
            close(sock);
        }
    }

    void serverio(int fd, int i)
    {
        char buffer[1024];
        ssize_t n = read(fd, buffer, sizeof(buffer) - 1);
        if (n < 0)
        {
            cout << "read fail" << endl;
            return;
        }
        else if (n == 0)
        {
            cout << "client close,me too" << endl;
            close(fd);
            _fdarr[i]=default_fd;
        }
        else
        {
            buffer[n - 1] = 0;
            cout << "client:" << buffer << endl;
            string echo = buffer;
            echo += " [select server echo]";
            send(fd, echo.c_str(), echo.size(), 0);
        }
    }

    void hand_event(const fd_set &rfds)
    {
        for (int i = 0; i < N; ++i)
        {
            if (_fdarr[i] == _listensock.Fd() && FD_ISSET(_listensock.Fd(), &rfds))
            {
                accepter();
            }
            else if (_fdarr[i] != _listensock.Fd() && FD_ISSET(_fdarr[i], &rfds))
            {
                serverio(_fdarr[i], i);
            }
        }
    }

    void printf_fd()
    {
        for (int i = 0; i < N; ++i)
        {
            if (_fdarr[i] != default_fd)
                cout << _fdarr[i] << " ";
        }
        cout<<endl;
    }
};

上面的程序进行serverio时还会存在着下面的两个问题:

  • 我们在readwrite时并没有自定义协议读取或者发送数据,由于我们采用的是TCP协议,读取或者发送数据时都是以字节流的形式进行的,所以会存在着粘包问题,因为我们并没有定制协议读取/发送一个完整的报文;
  • 在进行read读取完数据后,使用write发送数据时也要将fd交给select管理,因为写事件是不一定就绪的。

此时我们可以来验证下:
在这里插入图片描述当我们使用另外一个用户再发起请求:

在这里插入图片描述
然后让wjb用户退出:
在这里插入图片描述

3.3 🍎第二版本的SelectServer🍎

从select函数原型我们知道select等待的条件不仅有可读,还有可写与异常,假如我们想要同时处理可写与异常应该咋办呢?
其实并不难,我们可以将fd数组替换成一个带有事件方式的自定义类型的数组。
比如参考下面的这种方式:
在这里插入图片描述
修改后的版本:

const uint16_t g_port = 8899;
const int N = sizeof(fd_set) * 8;
const int default_fd = -1;

#define READ_EVENT 0X1
#define WRITE_EVENT 0X1 << 1
#define EXPECT_EVENT 0x1 << 2

const uint8_t default_event=READ_EVENT;
struct FdEvent
{
    int fd;
    uint8_t event;
    string clientip;
    uint16_t clientport;
};

class SelectServer
{
private:
    Sock _listensock;
    uint16_t _port;
    FdEvent _fdarr[N];

public:
    SelectServer(const uint16_t port = g_port)
        : _port(port)
    {
    }

    ~SelectServer()
    {
        _listensock.Close();
    }

    void init()
    {
        _listensock.Socket();
        _listensock.Bind(_port);
        _listensock.Listen();

        for (int i = 0; i < N; ++i)
        {
            _fdarr[i].fd = default_fd;
            _fdarr[i].event=default_event;
        }
    }

    void run()
    {

        _fdarr[0].fd = _listensock.Fd();
        while (true)
        {
            fd_set rfds;
            FD_ZERO(&rfds);
            int max_fd = _fdarr[0].fd;
            for (int i = 0; i < N; ++i)
            {
                if (_fdarr[i].fd != default_fd)
                    FD_SET(_fdarr[i].fd, &rfds);
                max_fd = max(max_fd, _fdarr[i].fd);
            }
            // struct timeval timeout = {2, 0};
            int n = select(max_fd + 1, &rfds, nullptr, nullptr, nullptr);
            if (n > 0)
            {
                cout << "有一个就绪事件发生了" << endl;
                // 表示已经有n个连接到来了,此时我们能够直接accept吗?
                hand_event(rfds);
                printf_fd();
            }
            else if (n == 0)
            {
                cout << "time out" << endl;
            }
            else
            {
                cout << "select errno:" << errno << ":" << strerror(errno) << endl;
            }
        }
    }

private:
    void accepter()
    {
        string clientip;
        uint16_t clientport;
        int sock = _listensock.Accept(&clientip, &clientport);
        cout << "[ip:port]:" << clientip << ":" << clientport << endl;
        int pos = 1;
        while (pos < N)
        {
            if (_fdarr[pos].fd == default_fd)
            {
                _fdarr[pos].fd = sock;
                break;
            }
            ++pos;
        }
        if (pos > N)
        {
            cout << "_fdarr full" << endl;
            close(sock);
        }
    }

    void serverio(int fd, int i)
    {
        char buffer[1024];
        ssize_t n = read(fd, buffer, sizeof(buffer) - 1);
        if (n < 0)
        {
            cout << "read fail" << endl;
            return;
        }
        else if (n == 0)
        {
            cout << "client close,me too" << endl;
            close(fd);
            _fdarr[i].fd=default_fd;
        }
        else
        {
            buffer[n - 1] = 0;
            cout << "client:" << buffer << endl;
            string echo = buffer;
            echo += " [select server echo]";
            send(fd, echo.c_str(), echo.size(), 0);
        }
    }

    void hand_event(const fd_set &rfds)
    {
        for (int i = 0; i < N; ++i)
        {
            if ((_fdarr[i].event & READ_EVENT) && FD_ISSET(_listensock.Fd(), &rfds))
            {
                if (_fdarr[i].fd == _listensock.Fd())
                {
                    accepter();
                }
                else if (_fdarr[i].fd != _listensock.Fd())
                {
                    serverio(_fdarr[i].fd, i);
                }
                else
                {

                }
            }
            else if((_fdarr[i].event & WRITE_EVENT) && FD_ISSET(_listensock.Fd(), &rfds))
            {
                
            }

        }
    }

    void printf_fd()
    {
        for (int i = 0; i < N; ++i)
        {
            if (_fdarr[i].fd != default_fd)
                cout << _fdarr[i].fd << " ";
        }
        cout<<endl;
    }
};

注意上面的代码中serverio还是有着第一个版本的两个问题:粘包问题和没有将write的fd也交给select管理(写事件不一定就绪)。

3.3 🍎socket就绪条件🍎

3.3.1 🍋读就绪🍋

  • socket内核中, 接收缓冲区中的字节数, 大于等于低水位标记SO_RCVLOWAT. 此时可以无阻塞的读该文件描述符, 并且返回值大于0;
  • socket TCP通信中, 对端关闭连接, 此时对该socket读, 则返回0;
  • 监听的socket上有新的连接请求;
  • socket上有未处理的错误;

3.3.2 🍋写就绪🍋

  • socket内核中, 发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小), 大于等于低水位标记SO_SNDLOWAT, 此时可以无阻塞的写, 并且返回值大于0;
  • socket的写操作被关闭(close或者shutdown). 对一个写操作被关闭的socket进行写操作, 会触发SIGPIPE信号;
  • socket使用非阻塞connect连接成功或失败之后;
  • socket上有未读取的错误;

3.4 🍎select缺点🍎

  • 每次调用select, 都需要手动设置fd集合, 从接口使用角度来说也非常不便;
  • 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大;
  • 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大;
  • select支持的文件描述符数量太小;

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

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

相关文章

【LearnOpenGL基础入门——1】OpenGL简介

目录 一.OpenGL是什么 二.渲染模式 三.前置知识 四.写在最后 一.OpenGL是什么 我们首先先了解一下OpenGL到底是什么。一般我们认为是包含了一系列可以操作图形、图像的函数的一套API。然而&#xff0c;OpenGL本身并不是一个API&#xff0c;它仅仅是一个由Khronos组织制定并…

Java 多线程的线程间的协作

1. 等待与通知 为了支持多线程之间的协作&#xff0c;JDK 中提供了两个非常重要的方法&#xff1a;wait() 和 notify() &#xff0c;这两个方法定义在 Object 类中&#xff0c;这意味着任何 Java 对象都可以调用者两个方法。如果一个线程调用了 object.wait() 方法&#xff0c;…

多线程JUC 第2季 多线程的内存模型

一 内存模型 1.1 概述 在hotspot虚拟机里&#xff0c;对象在堆内存中的存储布局可以划分为3个部分&#xff1a;对象头&#xff1b;实例数据&#xff0c;对齐填充。如下所示&#xff1a;

MES与ERP系统集成的一些探讨

什么是MES软件&#xff1f; 制造执行系统 &#xff08;MES&#xff09; 是一种用于控制车间复杂制造操作和数据的软件。MES软件有助于提高生产过程的质量&#xff0c;使制造商能够轻松响应需求和客户偏好的变化。 MES软件有什么作用&#xff1f; 制造执行系统允许企业跟踪、…

2、k8s 集群安装

1、kubeadm kubeadm 是官方社区推出的一个用于快速部署 kubernetes 集群的工具。 这个工具能通过两条指令完成一个 kubernetes 集群的部署&#xff1a; # 创建一个 Master 节点 $ kubeadm init # 将一个 Node 节点加入到当前集群中 $ kubeadm join <Master 节点的 IP 和端口…

操作系统——初始文件管理(王道视频p58)

1.总体概述&#xff1a; 这一节&#xff0c;主要是 作为 后续 “文件系统”的引子 我认为可以思考的点&#xff1a; &#xff08;1&#xff09;文件之间的逻辑结构——windows中采用根什么的“树状结构”&#xff0c;而文件在外存中的实际物理结构又是什么样的 &#xff08…

【并行计算】多核处理器

这张图连接了几个并行计算的思想。 从上往下。 1.两个fetch/decode部件&#xff0c;是superscalar技术&#xff0c;每个cycle可以发射多个指令。 2.多个执行单元&#xff0c;支持乱序执行&#xff0c;是ILP&#xff0c;指令级并行。 3.每个执行单元里还支持SIMD操作。 4.有…

求职应聘校招社招,面对在线测评有什么技巧?

网上测评&#xff0c;不要怕&#xff0c;关键是在于你要提前准备充分。要说技巧&#xff0c;真心没有&#xff0c;但是建议我有一点点。 1、网上测评&#xff0c;技巧就是老实做 老老实实做题&#xff0c;我一贯的作风&#xff0c;老实人不吃亏。越是心思灵巧的人&#xff0c…

MyBatis-Plus复习总结(一)

文章目录 一、环境搭键二、基本CRUD2.1 BaseMapper2.2 插入2.3 删除2.4 修改2.5 查询 三、通用Service四、常用注解4.1 雪花算法4.2 注解TableLogic 五、条件构造器和常用接口5.1 Wrapper介绍5.2 QueryWrapper5.3 UpdateWrapper5.4 condition5.5 LambdaQueryWrapper5.6 LambdaU…

二、计算机组成原理与体系结构

&#xff08;一&#xff09;数据的表示 不同进制之间的转换 R 进制转十进制使用按权展开法&#xff0c;其具体操作方式为&#xff1a;将 R 进制数的每一位数值用 Rk 形式表示&#xff0c;即幂的底数是 R &#xff0c;指数为 k &#xff0c;k 与该位和小数点之间的距离有关。当…

论文阅读—— UniDetector(cvpr2023)

arxiv&#xff1a;https://arxiv.org/abs/2303.11749 github&#xff1a;https://github.com/zhenyuw16/UniDetector 一、介绍 通用目标检测旨在检测场景那种的一切目标。现有的检测器依赖于大量数据集 通用的目标检测器应该有两个能力&#xff1a;1、可以利用多种来…

鹏城杯_2018_note

查看保护&#xff0c;就开了 PIE&#xff1a; 漏洞点&#xff1a; buf 存在溢出&#xff0c;刚好可以溢出到 idx&#xff0c;而且没有开 PIE 和 FULL RELRO&#xff0c;所以可以修改 idx 去修改相关 got 表项。 然后我就没啥思路了&#xff0c;因为在我的本地环境堆上是没有可…

Flink SQL时间属性和窗口介绍

&#xff08;1&#xff09;概述 时间属性&#xff08;time attributes&#xff09;&#xff0c;其实就是每个表模式结构&#xff08;schema&#xff09;的一部分。它可以在创建表的 DDL 里直接定义为一个字段&#xff0c;也可以在 DataStream 转换成表时定义。 一旦定义了时间…

一文通透各种注意力:从多头注意力MHA到分组查询注意力GQA、多查询注意力MQA

第一部分 多头注意力 // 待更 第二部分 LLaMA2之分组查询注意力——Grouped-Query Attention 自回归解码的标准做法是缓存序列中先前标记的键 (K) 和值 (V) 对&#xff0c;从而加快注意力计算速度 然而&#xff0c;随着上下文窗口或批量大小的增加&#xff0c;多头注意力 (MH…

Buuctf-Crypto-之深夜刷题部分wp

萌萌哒的八戒 首先下载好附件&#xff0c;解压&#xff0c;是一幅猪图&#xff0c;图的下方是一串看不懂的字&#xff0c;百度输入关键词猪、密码&#xff0c;可知这是猪圈密码&#xff0c; 手撸得WHENTHEPIGWANTTOEAT 大写不对&#xff0c;换成小写。 …

数据结构——常见简答题汇总

目录 1、绪论 2、线性表 3、栈、队列和数组 4、串 5、树与二叉树 6、图 7、查找 8、排序 1、绪论 什么是数据结构&#xff1f; 数据结构是相互之间存在一种或多种特定关系的数据元素的集合。数据结构包括三个方面&#xff1a;逻辑结构、存储结构、数据的运算。 逻辑结…

中小学智慧校园电子班牌管理系统源码

智慧校园云平台电子班牌系统&#xff0c;利用先进的云计算技术&#xff0c;将教育信息化资源和教学管理系统进行有效整合&#xff0c;实现基础数据共享、应用统一管理。借助全新的智能交互识别终端和移动化教育管理系统&#xff0c;以考勤、课表、通知、家校互通等功能为切入点…

如何将 XxlJob 集成达梦数据库

1. 前言 在某些情况下&#xff0c;你的项目可能会面临数据库选择的特殊要求&#xff0c;随着国产化的不断推进&#xff0c;达梦数据库是一个常见的选择。本篇博客将教你如何解决 XxlJob 与达梦数据库之间的 SQL 兼容性问题&#xff0c;以便你的任务调度系统能够在这个数据库中…

NNDL 作业6 卷积

一、概念 &#xff08;一&#xff09;卷积 &#xff08;1&#xff09;什么叫卷积 卷积、旋积或褶积(英语&#xff1a;Convolution)是通过两个函数f和g生成第三个函数的一种数学运算&#xff0c;其本质是一种特殊的积分变换&#xff0c;描述一个函数和另一个函数在某个维度上…

类和对象解析

导言&#xff1a; Java是一门纯面向对象的语言&#xff0c;在面对对象的世界里&#xff0c;一切皆为对象。而对象的创建又和类的定义息息相关。本文主要阐述了类和对象的使用与理解。解释类的定义方式以及对象的实例化&#xff0c;类中的成员变量和成员方法的使用&#xff0c;…