【网络进阶】服务器模型Reactor与Proactor

文章目录

    • 1. Reactor模型
    • 2. Proactor模型
    • 3. 同步IO模拟Proactor模型

在高并发编程和网络连接的消息处理中,通常可分为两个阶段:等待消息就绪和消息处理。当使用默认的阻塞套接字时(例如每个线程专门处理一个连接),这两个阶段往往是合并的。因此,处理套接字的线程需要等待消息就绪,这在高并发场景下导致线程频繁地休眠和唤醒,从而影响 CPU 使用效率。

为提高高并发编程效率,可将等待消息就绪和消息处理两个阶段分开。换言之,等待消息就绪的代码段与处理消息的代码段应分离。这要求套接字必须为非阻塞,否则处理消息的代码段可能导致线程在条件不满足时进入等待状态。实现等待消息就绪阶段的方法是线程主动查询,或让单个线程等待所有连接。这就引入了 I/O 多路复用技术,它可以同时处理多个连接,尽管仍可能需要等待并导致线程休眠,但由于它可以监控所有连接,所以当线程被唤醒时,必定有一些连接已准备好进行处理。

高性能服务器程序通常需要处理三类事件:I/O 事件,定时事件以及信号。为实现高效的事件处理,有两种主要模型:Reactor 和 Proactor。这两种模型分别采用不同的策略来解决等待消息就绪和消息处理阶段的问题,以提高服务器在高并发环境下的性能表现。

1. Reactor模型

首先让我们回顾一下常规函数调用的机制:程序调用某个函数,函数执行,程序等待,然后函数将结果和控制权返回给程序,程序继续处理。与此不同,Reactor(反应堆)是一种事件驱动的机制。它颠覆了传统的事件处理流程:应用程序不再主动调用某个 API 完成处理,而是将处理逻辑逆置。在这种模式下,应用程序需要提供相应的接口并将其注册到 Reactor 上。当相应的事件发生时,Reactor 会主动调用应用程序注册的接口,这些接口通常被称为“回调函数”。通过这种方式,Reactor 实现了事件驱动编程,提高了程序的响应速度和效率。
在这里插入图片描述
Reactor 模式是处理并发 I/O 的一种常见模式,适用于同步 I/O。其核心思想是将所有待处理的 I/O 事件注册到一个中心 I/O 多路复用器上,同时让主线程/进程在多路复用器上阻塞。当有 I/O 事件到来或准备就绪(如文件描述符或 socket 可读、写)时,多路复用器返回并将事先注册的相应 I/O 事件分发到对应的处理器中。这样,Reactor 模式能够实现高效的并发 I/O 处理,提高程序在高并发环境下的性能表现。

Reactor模型有三个重要的组件:

  • Reactor:Reactor 是核心组件,负责管理事件循环和I/O事件分发。它接收客户端连接,监听I/O事件,并将这些事件分发给相应的事件处理器。Reactor可以是单线程或多线程的,根据具体应用场景和性能要求进行选择。
  • Handlers:事件处理器(Handlers)是用于处理特定I/O事件的对象。每个处理器通常对应一个客户端连接或一个资源(如文件、套接字等)。处理器负责处理与它们关联的事件,如读取数据、处理业务逻辑、发送响应等。处理器可以是同步或异步的,取决于具体的实现方式。
  • Demultiplexer:事件分离器(Demultiplexer)负责从操作系统中获取I/O事件,并将这些事件传递给Reactor。常见的事件分离器实现方式有 select, poll, epoll, kqueue 等。事件分离器使Reactor能够同时处理多个I/O事件,从而实现高并发性能。

在这里插入图片描述

事件处理的具体流程:

  • Reactor 接收客户端连接,将连接的相关I/O事件注册到事件分离器(Demultiplexer)上。同时,Reactor 将事件处理器(Handlers)与这些I/O事件关联起来。
  • 事件分离器(Demultiplexer)负责检测并收集操作系统中发生的I/O事件。当有I/O事件发生时,事件分离器将这些事件通知Reactor。
  • Reactor 将收到的I/O事件与之前注册的事件处理器(Handlers)进行匹配。找到匹配的处理器后,Reactor 将事件分发给相应的处理器。
  • 事件处理器(Handlers)收到分发的I/O事件后,执行相应的操作,如读取数据、处理业务逻辑、发送响应等。
  • 事件处理完成后,Reactor 可以根据需要更新处理器与I/O事件之间的关联,以便继续处理后续事件。

Reactor模型的优点:

  • 高并发性能:Reactor模式通过事件驱动的方式实现了高并发性能。事件分离器可以同时检测多个I/O事件,Reactor可以根据事件分离器的通知并行处理多个I/O事件,从而提高服务器的并发处理能力。
  • 良好的可扩展性:Reactor模式可以通过增加线程数量或使用多个Reactor实例来提高系统的可扩展性。在多核处理器环境下,可以利用多线程Reactor模式有效地分摊负载,进一步提高系统性能。
  • 资源利用率高:Reactor模式中,事件处理器只在有I/O事件发生时才执行操作。这样可以避免无效的轮询和资源浪费,提高系统资源的利用率。
  • 易于管理和维护:Reactor模式将事件处理器和I/O事件分离,使得事件处理逻辑更加清晰和易于管理。此外,Reactor模式还可以实现处理器的动态注册和注销,便于系统的维护和扩展。
  • 可适应不同场景:Reactor模式可以根据不同的应用场景和性能要求进行定制。例如,可以选择单线程或多线程Reactor,同步或异步处理器等,以满足特定应用的需求。

Reactor 模型在开发效率方面相较于直接使用 IO 复用有所提高。该模型通常采用单线程设计,目标是让单线程充分利用一颗 CPU 的全部资源。同时,它还带有一个优点,即在处理事件时,很多情况下无需考虑共享资源的互斥访问问题。然而,这个模型也存在明显的缺点。随着硬件的发展,摩尔定律不再适用,CPU 频率由于材料限制无法继续大幅提升,因此提升性能主要依赖于增加核数。在需要利用多核资源的程序中,Reactor 模型的表现可能会受到影响。

对于业务较简单的程序,如仅需访问提供并发访问的服务,可以直接启用多个反应堆,每个反应堆对应一颗 CPU 核心。在这种情况下,运行在各个反应堆上的请求互不相关,从而充分利用多核资源。例如,Nginx 这类的 HTTP 静态服务器就采用了这种策略。


在C++里使用Reactor模型实现的TCP回显服务器:

需要系统支持epoll,并且编译器支持C++11

#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <vector>
#include <map>
#include <unistd.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/socket.h>

#define MAX_EVENTS 10

class Reactor {
public:
    Reactor() {
        _epoll_fd = epoll_create1(0);
        if (_epoll_fd == -1) {
            perror("epoll_create1");
            exit(EXIT_FAILURE);
        }
    }

    ~Reactor() {
        close(_epoll_fd);
    }

    void add_fd(int fd, uint32_t events) {
        struct epoll_event event;
        event.data.fd = fd;
        event.events = events;
        if (epoll_ctl(_epoll_fd, EPOLL_CTL_ADD, fd, &event) == -1) {
            perror("epoll_ctl");
            exit(EXIT_FAILURE);
        }
    }

    void del_fd(int fd) {
        if (epoll_ctl(_epoll_fd, EPOLL_CTL_DEL, fd, nullptr) == -1) {
            perror("epoll_ctl");
            exit(EXIT_FAILURE);
        }
    }

    void run() {
        std::vector<struct epoll_event> events(MAX_EVENTS);
        while (true) {
            int n = epoll_wait(_epoll_fd, events.data(), MAX_EVENTS, -1);
            if (n == -1) {
                perror("epoll_wait");
                exit(EXIT_FAILURE);
            }

            for (int i = 0; i < n; i++) {
                if (events[i].events & EPOLLIN) {
                    handle_input(events[i].data.fd);
                } else if (events[i].events & EPOLLOUT) {
                    handle_output(events[i].data.fd);
                }
            }
        }
    }

    virtual void handle_input(int fd) = 0;
    virtual void handle_output(int fd) = 0;

private:
    int _epoll_fd;
};

class EchoReactor : public Reactor {
public:
    EchoReactor(int listen_fd) : _listen_fd(listen_fd) {
        add_fd(_listen_fd, EPOLLIN);
    }

    void handle_input(int fd) override {
    if (fd == _listen_fd) {
        struct sockaddr_in addr;
        socklen_t addrlen = sizeof(addr);
        int conn_fd = accept(_listen_fd, (struct sockaddr *)&addr, &addrlen);
        if (conn_fd == -1) {
            perror("accept");
            exit(EXIT_FAILURE);
        }

        make_socket_non_blocking(conn_fd);
        add_fd(conn_fd, EPOLLIN);
    } else {
        char buf[1024];
        ssize_t n = read(fd, buf, sizeof(buf));
        if (n <= 0) {
            if (n < 0) perror("read");
            close(fd);
            del_fd(fd);
        } else {
            _out_buffers[fd] = std::string(buf, n);
            del_fd(fd);
            add_fd(fd, EPOLLOUT);
        }
    }
}

void handle_output(int fd) override {
    auto it = _out_buffers.find(fd);
    if (it != _out_buffers.end()) {
        ssize_t n = write(fd, it->second.c_str(), it->second.size());
        if (n <= 0) {
            if (n < 0) perror("write");
            close(fd);
            del_fd(fd);
        } else {
            it->second.erase(0, n);
            if (it->second.empty()) {
                del_fd(fd);
                add_fd(fd, EPOLLIN);
            }
        }
    }
}

private:
    int make_socket_non_blocking(int sfd) {
        int flags = fcntl(sfd, F_GETFL, 0);
        if (flags == -1) {
            perror("fcntl");
            return -1;
        }

        flags |= O_NONBLOCK;
        if (fcntl(sfd, F_SETFL, flags) == -1) {
            perror("fcntl");
            return -1;
        }

        return 0;
    }

    int _listen_fd;
    std::map<int, std::string> _out_buffers;
};

int main(int argc, char *argv[]) {
    if (argc != 2) {
        std::cerr << "Usage: " << argv[0] << " <port>" << std::endl;
        exit(EXIT_FAILURE);
    }

    int port = std::stoi(argv[1]);

    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    int optval = 1;
    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
        perror("bind");
        exit(EXIT_FAILURE);
    }

    if (listen(listen_fd, SOMAXCONN) == -1) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    EchoReactor echo_reactor(listen_fd);
    echo_reactor.run();

    return 0;
}

使用telnet测试:
在这里插入图片描述

2. Proactor模型

在众多网络编程模型中,Reactor模型因其高并发性能、资源利用率高等优点而受到广泛关注。Reactor模型基于事件驱动的原理,通过Reactor、事件处理器(Handlers)和事件分离器(Demultiplexer)三个核心组件的协同作用,有效地处理大量并发连接和I/O操作。然而,在某些高负载场景下,Reactor模型可能会遇到事件处理瓶颈的问题,这使得我们需要寻求其他解决方案来进一步提升系统性能。

Proactor模型正是在这种背景下应运而生的一种高性能网络编程模型。与Reactor模型类似,Proactor模型同样采用了事件驱动的方式,但在事件处理的实现上有所不同。Proactor模型将异步I/O操作与事件处理器相结合,使得I/O操作可以在后台执行,从而进一步减少阻塞和提高系统性能。在Proactor模型中,事件处理的主要责任由操作系统承担,而应用程序则专注于处理业务逻辑。这种分工使得Proactor模型能够更好地应对高负载场景,为构建高性能网络服务器提供了一种有力的解决方案。

在这里插入图片描述

Proactor模型有三个重要的组件:

  • Proactor:Proactor是核心组件,负责管理事件循环和I/O事件分发。它接收客户端连接,监听I/O事件,并将这些事件分发给相应的事件处理器。与Reactor模型相比,Proactor将大部分I/O操作的处理交给操作系统,从而进一步减少阻塞和提高系统性能。
  • Asynchronous Handlers:异步事件处理器(Asynchronous Handlers)是用于处理特定I/O事件的对象。每个处理器通常对应一个客户端连接或一个资源(如文件、套接字等)。与Reactor模型中的事件处理器不同,Proactor模型中的处理器通过异步I/O操作与事件处理相结合,使得I/O操作可以在后台执行,进一步提高系统性能。
  • Asynchronous Operation Processor:异步操作处理器(Asynchronous Operation Processor)是负责执行实际的异步I/O操作的组件。它与事件处理器协同工作,将I/O操作的执行与事件处理解耦,进一步减少阻塞和提高系统性能。
  • Completion Dispatcher:完成分发器(Completion Dispatcher)负责在异步I/O操作完成后通知Proactor。当操作系统完成一个异步I/O操作时,它会将完成通知发送给Completion Dispatcher,而后者则将通知传递给Proactor。这样,Proactor可以将事件分发给相应的事件处理器,处理业务逻辑。

在这里插入图片描述

事件处理的具体流程:

  • Proactor 接收客户端连接,并将连接的相关I/O事件注册到异步事件处理器(Asynchronous Handlers)上。
  • 异步事件处理器(Asynchronous Handlers)启动异步I/O操作。此时,I/O操作的执行由异步操作处理器(Asynchronous Operation Processor)负责,与事件处理器解耦,使得I/O操作可以在后台执行。
  • 异步操作处理器(Asynchronous Operation Processor)与操作系统协同工作,执行实际的异步I/O操作。这样,事件处理器可以在等待I/O操作完成的过程中处理其他任务,进一步提高系统性能。
  • 当操作系统完成一个异步I/O操作时,它会将完成通知发送给完成分发器(Completion Dispatcher)。
  • 完成分发器(Completion Dispatcher)收到通知后,将通知传递给Proactor。此时,Proactor将事件分发给相应的异步事件处理器(Asynchronous Handlers)。
  • 异步事件处理器(Asynchronous Handlers)收到分发的I/O事件后,执行相应的操作,如读取数据、处理业务逻辑、发送响应等。

从上述处理流程中,我们可以发现 Proactor 模型的最大特点是采用异步 I/O。所有的 I/O 操作都由系统提供的异步 I/O 接口执行,工作线程仅负责处理业务逻辑。在 Proactor 模型中,用户函数启动一个异步文件操作并将其注册到多路复用器上。多路复用器关注的是异步读操作是否完成,而不是文件是否可读或可写。异步操作由操作系统完成,用户程序无需关心。当操作系统完成读文件操作,即将数据复制到用户先前提供的缓冲区后,便通知多路复用器相关操作已完成。多路复用器随后调用相应的处理程序处理数据。

尽管 Proactor 增加了编程的复杂度,但它提高了工作线程的效率。Proactor 可在系统态优化读写操作,利用 I/O 并行能力,从而实现高性能单线程模型。在 Windows 上,由于缺乏类似 epoll 的机制,因此采用 IOCP 支持高并发。由于操作系统进行了良好的优化,Windows 更常使用基于完成端口的 Proactor 模型实现服务器。而在 Linux 上,虽然 2.6 内核引入了 aio 接口,但实际效果并不理想。aio 的出现主要是为了解决 poll 性能不佳的问题,但实际测试表明,epoll 的性能高于 poll+aio,且 aio 无法处理 accept。因此,Linux 主要还是以 Reactor 模型为主。

在不使用操作系统提供的异步 I/O 接口的情况下,确实可以通过 Reactor 模拟 Proactor。区别在于:利用异步接口可以使用系统提供的读写并行能力,而在模拟情况下,这需要在用户态实现。具体的做法包括以下步骤:

  1. 注册读事件(同时提供一段缓冲区)。
  2. 事件分离器等待可读事件。
  3. 事件到来时,激活分离器,分离器立即读取数据并写入缓冲区,然后调用事件处理器。
  4. 事件处理器处理数据,删除事件(需要再用异步接口注册)。

值得注意的是,Boost.asio 库采用的是 Proactor 模型。然而,在 Linux 平台上,Boost.asio 使用 epoll 实现的 Reactor 来模拟 Proactor,并额外开辟一个线程来完成读写调度。这种做法在一定程度上结合了两种模型的优势,实现了较高的性能和灵活性。


在C++里使用Proactor模型实现的TCP回显服务器:

这个示例基于Boost.Asio库,它是一个跨平台的C++库,用于编写基于异步I/O操作的网络和低级I/O程序,使用前请确保你已经正确安装,并配置好共享库文件。

#include <iostream>
#include <memory>
#include <utility>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

class session : public std::enable_shared_from_this<session> {
public:
    session(tcp::socket socket) : socket_(std::move(socket)) {}

    void start() { read(); }

private:
    void read() {
        auto self(shared_from_this());
        socket_.async_read_some(boost::asio::buffer(data_, max_length),
            [this, self](boost::system::error_code ec, std::size_t length) {
                if (!ec) {
                    write(length);
                }
            });
    }

    void write(std::size_t length) {
        auto self(shared_from_this());
        boost::asio::async_write(socket_, boost::asio::buffer(data_, length),
            [this, self](boost::system::error_code ec, std::size_t /*length*/) {
                if (!ec) {
                    read();
                }
            });
    }

    tcp::socket socket_;
    enum { max_length = 1024 };
    char data_[max_length];
};

class server {
public:
    server(boost::asio::io_context& io_context, short port)
        : acceptor_(io_context, tcp::endpoint(tcp::v4(), port)) {
        accept();
    }

private:
    void accept() {
        acceptor_.async_accept(
            [this](boost::system::error_code ec, tcp::socket socket) {
                if (!ec) {
                    std::make_shared<session>(std::move(socket))->start();
                }
                accept();
            });
    }

    tcp::acceptor acceptor_;
};

int main(int argc, char* argv[]) {
    try {
        if (argc != 2) {
            std::cerr << "Usage: echo_server <port>\n";
            return 1;
        }

        boost::asio::io_context io_context;
        server s(io_context, std::atoi(argv[1]));
        io_context.run();
    } catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << "\n";
    }

    return 0;
}

使用telnet测试:
在这里插入图片描述

3. 同步IO模拟Proactor模型

在这里插入图片描述

  1. 主线程将 socket 上的读就绪事件注册到 epoll 内核事件表中。
  2. 主线程调用 epoll_wait 等待 socket 上的数据可读事件。
  3. 当 socket 上有数据可读时,epoll_wait 通知主线程。主线程循环读取 socket 上的数据,直到无更多数据可读,然后将读取到的数据封装为请求对象并插入请求队列。
  4. 在请求队列上等待的某个工作线程被唤醒,获取请求对象并处理客户请求,然后在 epoll 内核事件表中注册 socket 上的写就绪事件。
  5. 主线程调用 epoll_wait 等待 socket 的可写事件。
  6. 当 socket 可写时,epoll_wait 通知主线程。主线程将服务器处理客户请求的结果写入 socket。

两种模式的相同之处在于它们都涉及对某个 I/O 事件的通知(即通知某个模块,这个 I/O 操作可以进行或已经完成)。在结构上,两者也有共同点:demultiplexor 负责提交 I/O 操作(异步)、查询设备是否可操作(同步),当条件满足时,回调注册的处理函数。

它们的不同之处在于:在 Proactor(异步)情况下,回调注册的处理函数时,表示 I/O 操作已经完成;而在 Reactor(同步)情况下,回调注册的处理函数时,表示 I/O 设备可以进行某个操作(可读或可写),此时注册的处理函数开始提交操作。


在C++中,我们可以使用Boost.Asio库来使用步同步I/O模拟Proactor模型。以下是使用Boost.Asio实现Proactor模型的步骤:

  1. 安装Boost库:首先,确保已经安装了Boost库,并将其包含在项目中。Boost.Asio是Boost库的一部分。
  2. 包含所需的头文件:
    #include <boost/asio.hpp>
    #include <boost/bind.hpp>
    #include <iostream>
    #include <vector>
    
  3. 创建异步回调函数:为了模拟Proactor模型,我们需要创建异步回调函数,该函数在异步操作完成时被调用。
    void handle_read(const boost::system::error_code& error, std::size_t bytes_transferred) {
        if (!error) {
            std::cout << "Read: " << bytes_transferred << " bytes" << std::endl;
        } else {
            std::cerr << "Error: " << error.message() << std::endl;
        }
    }
    
  4. 使用Boost.Asio创建异步操作:使用Boost.Asio创建一个异步读取操作,并将其与异步回调函数关联。
    boost::asio::io_context io_context;
    boost::asio::ip::tcp::socket socket(io_context);
    boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::address::from_string("127.0.0.1"), 8080);
    
    socket.async_connect(endpoint, boost::bind(&handle_read, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
    
  5. 运行I/O上下文:运行I/O上下文以处理异步操作。
    io_context.run();
    

完整的代码如下:

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <vector>

void handle_read(const boost::system::error_code& error, std::size_t bytes_transferred) {
    if (!error) {
        std::cout << "Read: " << bytes_transferred << " bytes" << std::endl;
    } else {
        std::cerr << "Error: " << error.message() << std::endl;
    }
}

int main() {
    try {
        boost::asio::io_context io_context;
        boost::asio::ip::tcp::socket socket(io_context);
        boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::address::from_string("127.0.0.1"), 8080);

        socket.async_connect(endpoint, boost::bind(&handle_read, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));

        io_context.run();
    } catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }

    return 0;
}

测试使用的TCP服务器来接受上面客户端的连接:

#include <boost/asio.hpp>
#include <iostream>
#include <array>
#include <memory>

class session : public std::enable_shared_from_this<session> {
public:
    session(boost::asio::ip::tcp::socket socket) : socket_(std::move(socket)) {}

    void start() { read(); }

private:
    void read() {
        auto self(shared_from_this());
        socket_.async_read_some(boost::asio::buffer(data_),
                                [this, self](boost::system::error_code ec, std::size_t length) {
                                    if (!ec) {
                                        read();
                                    }
                                });
    }

    boost::asio::ip::tcp::socket socket_;
    std::array<char, 1024> data_;
};

class server {
public:
    server(boost::asio::io_context& io_context, short port)
        : acceptor_(io_context, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)) {
        accept();
    }

private:
    void accept() {
        acceptor_.async_accept([this](boost::system::error_code ec, boost::asio::ip::tcp::socket socket) {
            if (!ec) {
                std::make_shared<session>(std::move(socket))->start();
            }

            accept();
        });
    }

    boost::asio::ip::tcp::acceptor acceptor_;
};

int main() {
    try {
        boost::asio::io_context io_context;
        server srv(io_context, 8080);
        io_context.run();
    } catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }

    return 0;
}

makefile:

.PHONY:clean
all: client.cc server.cc
        g++ client.cc -o client -I/usr/local/include -L/usr/local/lib -lpthread -std=c++11
        g++ server.cc -o server -I/usr/local/include -L/usr/local/lib -lpthread -std=c++11
clean:
        rm -f client server

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

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

相关文章

【redis】redis分布式锁(二)可重入锁+设计模式

【redis】redis分布式锁&#xff08;二&#xff09;可重入锁 文章目录 【redis】redis分布式锁&#xff08;二&#xff09;可重入锁前言一、可重入锁&#xff08;又名递归锁&#xff09;1、说明&#xff1a;2、分开解释&#xff1a;3、可重入锁的种类隐式锁&#xff08;即synch…

【软件测试】测试用例的设计

文章目录 一. 针对没有需求的案例来设计测试用例二. 针对有需求的案例来设计测试用例1. 穷举法2. 等价类3. 边界值4. 判定表法5. 场景设计法5.1 简介5.2 基本设计步骤5.3 基本流和备选流5.4 使用场景5.5 优缺点5.6 实例 6. 错误猜测法 一. 针对没有需求的案例来设计测试用例 针…

深度强化学习——蒙特卡洛算法(6)

注&#xff1a;本章的内容作为补充插曲&#xff0c;大家可以选看&#xff0c;不过还是建议把最后一个使用蒙特卡洛近似求期望稍微看一下 蒙特卡洛是一大堆随机算法&#xff0c;通过随机样本来估算真实值 使用随机样本来近似Π 1、在[a,b]做随机均匀抽样&#xff0c;抽出n个样…

YOLO物体检测系列1.经典方法概述及评价指标体现

1. 深度学习经典检测方法&#xff1a; two-stage(两阶段)&#xff1a; Faster-rcnn Mask-RCNN系列 one-stage(单阶段)&#xff1a;Yolo系列 两阶段&#xff1a;一阶段实现RPN候选区域预选 二阶段基于候选区域再进行检测回归分类任务 单阶段&#xff1a;一个CNN卷积网络实现检测…

C++线程的简单学习及了解

此篇文章只是线程的简单了解。 文章目录 前言一、线程的优缺点二、C线程库 1.thread类的简单介绍2.线程函数参数总结 前言 什么是线程&#xff1f; 在一个程序里的一个执行路线就叫做线程&#xff08;thread&#xff09;。更准确的定义是&#xff1a;线程是“一个进程内部的控…

day3 TCP/IP协议与五层体系结构

TCP / IP 四层体系结构 TCP / IP工作流程&#xff1a; 现在互联网使用的 TCP/IP 体系结构已经发生了演变&#xff0c;即某些应用程序可以直接使用 IP 层&#xff0c;或甚至直接使用最下面的网络接口层。 沙漏型展示&#xff1a; 五层体系结构 各层的主要功能 应用层&#xff1…

搭建外网minecraft服务器方案

很多minecraft服务器主都想自己搭建一个外网可以访问的minecraft服务器&#xff0c;在没有外网IP的情况下&#xff0c;一般都是使用Logmein Hamachi方案。这种方案有它的弊端&#xff0c;需要客户机安装Hamachi&#xff0c;十分不方便。另外&#xff0c;免费版只支持5人&#x…

mysql如何加行锁

一、概述 InnoDB 引擎是支持行级锁的&#xff0c;而 MyISAM 引擎并不支持行级锁&#xff0c;所以后面的内容都是基于 InnoDB 引擎的。当我们使用delete、update进行数据库删除、更新的时候&#xff0c;数据库会自动加上行锁。但是&#xff0c;行锁有时也会失效。 数据库版本&a…

笔记:计算机网络体系结构(OSI七层模型、TCP/IP五层协议)

计算机网络体系结构 计算机网络是一个复杂的、具有综合性技术的系统&#xff0c;它由计算机系统、通信处理机、通信线路和通信设备、操作系统以及网络协议等组成。为了更好地描述计算机网络结构&#xff0c;使计算机网络系统有条不紊地处理工作&#xff0c;需要定义一种较好的…

CH9121网络串口透传应用

概述 随着物联网技术的普及&#xff0c;越来越多的传统设备出现联网功能需求。串口作为使用较为广泛的一种通信接口&#xff0c;串口转以太网&#xff0c;进行远程数据传输需求逐渐显现出来。CH9121内部集成TCP/IP协议栈&#xff0c;无需编程&#xff0c;即可轻松实现网络数据…

【SWAT水文模型】SWAT水文模型建立及应用第二期:土地利用数据的准备

SWAT水文模型建立及应用&#xff1a;土地利用数据的准备 1 简介2 土地利用数据的下载2.1 数据下载方式2.1.1 中科院1km土地利用数据2.1.2 清华大学高精度土地利用数据 2.2 数据下载 3 土地利用数据的准备3.1 矢量转栅格3.2 土地利用类型的重分类3.3 土地利用分布图投影调整3.4 …

【LeetCode】213. 打家劫舍 II

213. 打家劫舍 II&#xff08;中等&#xff09; 思路 这道题是 198.打家劫舍 的拓展版&#xff0c;区别在于&#xff1a;本题的房间是环形排列&#xff0c;而198.题中的房间是单排排列。 将房间环形排列&#xff0c;意味着第一间房间和最后一间房间不能同时盗窃&#xff0c;因…

EPIT定时器实验(一)

EPIT定时器简介 EPIT&#xff1a;Enhanced Periodic Interrupt Timer&#xff0c;直译就是增强的周期中断定时器&#xff0c;它主要完成周期性中断定时的。 STM32里面的定时器有很多其它功能&#xff0c;比如输入捕获、PWM输出等&#xff0c;但是I.MX6U的的EPIT定时器只是完成…

【五一创作】数据可视化之美 ( 三 ) - 动图展示 ( Python Matlab )

1 Introduction 在我们科研学习、工作生产中&#xff0c;将数据完美展现出来尤为重要。 数据可视化是以数据为视角&#xff0c;探索世界。我们真正想要的是 — 数据视觉&#xff0c;以数据为工具&#xff0c;以可视化为手段&#xff0c;目的是描述真实&#xff0c;探索世界。 …

CSS布局之圣杯布局/双飞翼布局

&#x1f4dd;个人主页&#xff1a;爱吃炫迈 &#x1f48c;系列专栏&#xff1a;HTMLCSS &#x1f9d1;‍&#x1f4bb;座右铭&#xff1a;道阻且长&#xff0c;行则将至&#x1f497; 文章目录 圣杯布局HTML代码步骤CSS代码 双飞翼布局HTML代码步骤CSS代码 小结 圣杯布局 HTM…

Java --- springboot2的静态资源配置原理

目录 一、静态资源配置原理 1.1、配置类只有一个有参构造器 1.2、资源处理的默认规则 1.3、欢迎页的处理规则 一、静态资源配置原理 springboot启动默认加载xxxAutoConfiguration(自动配置) springmvc功能的自动配置类&#xff0c;生效 Configuration(proxyBeanMethods …

《编码——隐匿在计算机软硬件背后的语言》精炼——第13-14章(二进制减法器——1位存储器)

“成功不是最终的&#xff0c;失败不是致命的&#xff0c;勇气才是最关键的。” - 温斯顿丘吉尔 文章目录 如何实现减法计算机进行减法运算的逻辑借位的代替机制二进制下的替代机制 减法的电路实现 反馈与触发器电铃触发器R-S触发器 电平触发的D型触发器 如何实现减法 计算机进…

霍兰德人格分析雷达图

雷达图 Radar Chart 雷达图是多特性直观展示的重要方式 问题分析 霍兰德认为&#xff1a;人格兴趣与职业之间应有一种内在的对应关系 人格分类&#xff1a;研究型、艺术型、社会型、企业型、传统型、现实性 职业&#xff1a;工程师、实验员、艺术家、推销员、记事员、社会工…

1992-2022年31省人均gdp/各省人均地区生产总值

1992-2022年31省人均gdp/各省人均地区生产总值 1、时间&#xff1a;1992-2022年 2、来源&#xff1a;国家统计J、各省NJ 3、范围&#xff1a;包括31省 4、缺失情况说明&#xff1a;无缺失 5、指标包括&#xff1a;各省人均GDP/省人均地区生产总值 6、指标解释&#xff1a…

五一劳动节前 特辑 ,路上那些车不能碰 你赔不起系列

相信明天大家4月29日都上了高速&#xff0c;都奔赴自己今年第一个想去的地方&#xff0c;那么上了高速&#xff0c;见的车辆就多了&#xff0c;哪些车辆我们要明白&#xff0c;尽量不要去碰&#xff0c;或者看见进行 技术性躲避&#xff0c;因为碰一下&#xff0c;半套房没了&a…