网络IO模型及零拷贝问题

news2025/3/1 11:24:02

文章目录

  • BIO
    • accept监听案例
    • read案例
      • 利用多线程
  • NIO
    • 案例
    • NIO存在的问题
  • IO multiplexing-IO多路复用
      • 文件描述符(FD,句柄)
    • 是什么
      • reactor反应模式
    • select方法
      • 优点
      • 缺点
      • 小总结
    • poll方法
      • 优点
      • 缺点
    • epoll方法
  • Reactor模式
    • 单Reactor单线程
  • 单Reactor多线程
  • 主从Reactor多线程
  • Netty模型
  • 零拷贝
    • 传统 IO 问题
    • NIO 优化
    • 进一步优化(linux 2.4)

BIO

  • 当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据(对于网络IO来说,很多时候数据在一开始还没有到达。比如,还没有收到一个完整的UDP包。这个时候kernel就要等待足够的数据到来)。这个过程需要等待,也就是说数据被拷贝到操作系统内核的缓冲区中是需要一个过程的

  • 在用户进程这边,整个进程会被阻塞(当然,是进程自己选择的阻塞)。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。

  • 所以,BIO的特点就是在IO执行的两个阶段都被block了。

  • image.png

image.png

accept监听案例

public class RedisServer
{
    public static void main(String[] args) throws IOException
    {
        byte[] bytes = new byte[1024];

        ServerSocket serverSocket = new ServerSocket(6379);

        while(true)
        {
            System.out.println("-----111 等待连接");
            Socket socket = serverSocket.accept();
            System.out.println("-----222 成功连接");
        }
    }
}
public class RedisClient02
{
    public static void main(String[] args) throws IOException
    {
        System.out.println("------RedisClient02 start");
        Socket socket = new Socket("127.0.0.1", 6379);
    }
}
 

read案例

public class RedisServerBIO
{
    public static void main(String[] args) throws IOException
    {

        ServerSocket serverSocket = new ServerSocket(6379);

        while(true)
        {
            System.out.println("-----111 等待连接");
            Socket socket = serverSocket.accept();//阻塞1 ,等待客户端连接
            System.out.println("-----222 成功连接");

            InputStream inputStream = socket.getInputStream();
            int length = -1;
            byte[] bytes = new byte[1024];
            System.out.println("-----333 等待读取");
            while((length = inputStream.read(bytes)) != -1)//阻塞2 ,等待客户端发送数据
            {
                System.out.println("-----444 成功读取"+new String(bytes,0,length));
                System.out.println("====================");
                System.out.println();
            }
            inputStream.close();
            socket.close();
        }
    }
}

public class RedisClient01
{
    public static void main(String[] args) throws IOException
    {
        Socket socket = new Socket("127.0.0.1",6379);
        OutputStream outputStream = socket.getOutputStream();
        while(true)
        {
            Scanner scanner = new Scanner(System.in);
            String string = scanner.next();
            if (string.equalsIgnoreCase("quit")) {
                break;
            }
            socket.getOutputStream().write(string.getBytes());
            System.out.println("------input quit keyword to finish......");
        }
        outputStream.close();
        socket.close();
    }
}

上面的模型存在很大的问题,如果客户端与服务端建立了连接,
如果这个连接的客户端迟迟不发数据,程序就会一直堵塞在read()方法上,这样其他客户端也不能进行连接,
也就是一次只能处理一个客户端,对客户很不友好。

利用多线程

只要连接了一个socket,操作系统分配一个线程来处理,这样read()方法堵塞在每个具体线程上而不堵塞主线程,
就能操作多个socket了,哪个线程中的socket有数据,就读哪个socket,各取所需,灵活统一。

public static void main(String[] args) throws IOException
{
    ServerSocket serverSocket = new ServerSocket(6379);
    while(true)
    {
        System.out.println("-----111 等待连接");
        Socket socket = serverSocket.accept();//阻塞1 ,等待客户端连接
        System.out.println("-----222 成功连接");

        new Thread(() -> {
            try {
                InputStream inputStream = socket.getInputStream();
                int length = -1;
                byte[] bytes = new byte[1024];
                System.out.println("-----333 等待读取");
                while((length = inputStream.read(bytes)) != -1)//阻塞2 ,等待客户端发送数据
                {
                    System.out.println("-----444 成功读取"+new String(bytes,0,length));
                    System.out.println("====================");
                    System.out.println();
                }
                inputStream.close();
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        },Thread.currentThread().getName()).start();
    }
}

多线程模型
每来一个客户端,就要开辟一个线程,如果来1万个客户端,那就要开辟1万个线程。
在操作系统中用户态不能直接开辟线程,需要调用内核来创建的一个线程,
这其中还涉及到用户状态的切换(上下文的切换),十分耗资源。

解决方法:
第一个办法:使用线程池
这个在客户端连接少的情况下可以使用,但是用户量大的情况下,你不知道线程池要多大,太大了内存可能不够,也不可行。

第二个办法:NIO(非阻塞式IO)方式
因为read()方法堵塞了,所有要开辟多个线程,如果什么方法能使read()方法不堵塞,这样就不用开辟多个线程了,这就用到了另一个IO模型,NIO(非阻塞式IO)。

NIO

  • 当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。

  • 从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。

  • 用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。

  • 一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。

  • 所以,NIO特点是用户进程需要不断的主动询问内核数据准备好了吗?一句话,用轮询替代阻塞!

image.png
在NIO模式中,一切都是非阻塞的:

accept()方法是非阻塞的,如果没有客户端连接,就返回无连接标识
read()方法是非阻塞的,如果read()方法读取不到数据就返回空闲中标识,如果读取到数据时只阻塞read()方法读数据的时间

在NIO模式中,只有一个线程:
当一个客户端与服务端进行连接,这个socket就会加入到一个数组中,隔一段时间遍历一次,
看这个socket的read()方法能否读到数据,这样一个线程就能处理多个客户端的连接和读取了。

案例

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;

public class RedisServerNIO
{
    static ArrayList<SocketChannel> socketList = new ArrayList<>();
    static ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

    public static void main(String[] args) throws IOException
    {
        System.out.println("---------RedisServerNIO 启动等待中......");
        ServerSocketChannel serverSocket = ServerSocketChannel.open();
        serverSocket.bind(new InetSocketAddress("127.0.0.1",6379));
        serverSocket.configureBlocking(false);//设置为非阻塞模式

        while (true)
        {
            for (SocketChannel element : socketList)
            {
                int read = element.read(byteBuffer);
                if(read > 0)
                {
                    System.out.println("-----读取数据: "+read);
                    byteBuffer.flip();
                    byte[] bytes = new byte[read];
                    byteBuffer.get(bytes);
                    System.out.println(new String(bytes));
                    byteBuffer.clear();
                }
            }

            SocketChannel socketChannel = serverSocket.accept();
            if(socketChannel != null)
            {
                System.out.println("-----成功连接: ");
                socketChannel.configureBlocking(false);//设置为非阻塞模式
                socketList.add(socketChannel);
                System.out.println("-----socketList size: "+socketList.size());
            }
        }
    }
}

NIO存在的问题

NIO成功的解决了BIO需要开启多线程的问题,NIO中一个线程就能解决多个socket,但是还存在2个问题。

问题一:
这个模型在客户端少的时候十分好用,但是客户端如果很多,
比如有1万个客户端进行连接,那么每次循环就要遍历1万个socket,如果一万个socket中只有10个socket有数据,也会遍历一万个socket,就会做很多无用功,每次遍历遇到 read 返回 -1 时仍然是一次浪费资源的系统调用。

问题二:
而且这个遍历过程是在用户态进行的,用户态判断socket是否有数据还是调用内核的read()方法实现的,这就涉及到用户态和内核态的切换,每遍历一个就要切换一次,开销很大

优点:不会阻塞在内核的等待数据过程,每次发起的 I/O 请求可以立即返回,不用阻塞等待,实时性较好。
缺点:轮询将会不断地询问内核,这将占用大量的 CPU 时间,系统资源利用率较低,所以一般 Web 服务器不使用这种 I/O 模型。

  • **结论:**让Linux内核搞定上述需求,我们将一批文件描述符通过一次系统调用传给内核由内核层去遍历,才能真正解决这个问题。IO多路复用应运而生,也即将上述工作直接放进Linux内核,不再两态转换而是直接从内核获得结果,因为内核是非阻塞的。

IO multiplexing-IO多路复用

文件描述符(FD,句柄)

文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。**文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。**当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。

是什么

一种同步的IO模型,实现一个线程监视多个文件句柄,一旦某个文件句柄就绪就能够通知到对应应用程序进行相应的读写操作,没有文件句柄就绪时就会阻塞应用程序,从而释放CPU资源。

**I/O:**网络IO,尤其在OS层面指数据在内核态和用户态之间的读写操作
**多路:**多个客户端连接
**复用:**复用一个或者几个线程
IO多路复用:也就是说一个或一组线程处理多个TCP连接,使用单进程就能够实现同时处理多个客户端的连接无需创建或者维护过多的进程/线程。
一句话:
就是一个服务端进程可以同时处理多个套接字描述符。

:::warning
将用户socket对应的文件描述符(FileDescriptor)注册进epoll,然后epoll帮你监听哪些socket上有消息到达,这样就避免了大量的无用操作。此时的socket应该采用非阻塞模式。这样,整个过程只在调用**select、poll、epoll**这些调用的时候才会阻塞,收发客户消息是不会阻塞的,整个进程或者线程就被充分利用起来,这就是事件驱动,所谓的**reactor**反应模式。
:::

reactor反应模式

Reactor 模式,是指通过一个或多个输入同时传递给服务处理器的服务请求的事件驱动处理模式。服务端程序处理传入多路请求,并将它们同步分派给请求对应的处理线程,Reactor 模式也叫 Dispatcher 模式。即 I/O 多路复用统一监听事件,收到事件后分发(Dispatch 给某进程),是编写高性能网络服务器的必备技术。

Reactor 模式中有 2 个关键组成:
1)Reactor:Reactor 在一个单独的线程中运行,负责监听和分发事件,分发给适当的处理程序来对 IO 事件做出反应。 它就像公司的电话接线员,它接听来自客户的电话并将线路转移到适当的联系人;
2)Handlers:处理程序执行 I/O 事件要完成的实际事件,类似于客户想要与之交谈的公司中的实际办理人。Reactor 通过调度适当的处理程序来响应 I/O 事件,处理程序执行非阻塞操作。

大家都用过nginx,nginx使用epoll接收请求,ngnix会有很多链接进来, epoll会把他们都监视起来,然后像拨开关一样,谁有数据就拨向谁,然后调用相应的代码处理。redis类似同理,这就是IO多路复用原理,有请求就响应,没请求不打扰。
:::success
实现IO多路复用的模型有3种:可以分为select->poll->epoll三个阶段来描述
:::

select方法

image.png

  • select 函数监视的文件描述符分3类,分别是readfds、writefds和exceptfds,将用户传入的数组拷贝到内核空间
  • 调用后select函数会阻塞,直到有描述符就绪(有数据 可读、可写、或者有except)或超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。
  • 当select函数返回后,可以通过遍历fdset,来找到就绪的描述符。

image.png
image.png
image.png

优点

select 其实就是把NIO中用户态要遍历的fd数组(我们的每一个socket链接,安装进ArrayList里面的那个)拷贝到了内核态,让内核态来遍历,因为用户态判断socket是否有数据还是要调用内核态的,拷贝到内核态后,这样遍历判断的时候就不用一直用户态和内核态频繁切换了

缺点

1、bitmap最大1024位,一个进程最多只能处理1024个客户端
2、&rset不可重用,每次socket有数据就相应的位会被置位
3、文件描述符数组拷贝到了内核态(只不过无系统调用切换上下文的开销。(内核层可优化为异步事件通知)),仍然有开销。select 调用需要传入 fd 数组,需要拷贝一份到内核,高并发场景下这样的拷贝消耗的资源是惊人的。(可优化为不复制)
4、select并没有通知用户态哪一个socket有数据,仍然需要O(n)的遍历。select 仅仅返回可读文件描述符的个数,具体哪个可读还是要用户自己遍历。(可优化为只返回给用户就绪的文件描述符,无需用户做无效的遍历)

小总结

select方式,既做到了一个线程处理多个客户端连接(文件描述符),又减少了系统调用的开销(多个文件描述符只有一次 select 的系统调用 + N次就绪状态的文件描述符的 read 系统调用)。

poll方法

image.png
image.png
image.png

优点

1、poll使用pollfd数组来代替select中的bitmap,数组没有1024的限制,可以一次管理更多的client。它和 select 的主要区别就是,去掉了 select 只能监听 1024 个文件描述符的限制。

2、当pollfds数组中有事件发生,相应的revents置位为1,遍历的时候又置位回零,实现了pollfd数组的重用

缺点

1、pollfds数组拷贝到了内核态,仍然有开销
2、poll并没有通知用户态哪一个socket有数据,仍然需要O(n)的遍历

epoll方法

image.png
image.png

int epoll_create(int size)参数size并不是限制了epoll所能监听听述符最大个数,只是对内核初始分配内部数据结构的一个建议
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)见上图
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)等待epfd上的io事件,最多返回maxevents个事件。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大。

image.png
image.png
多路复用快的原因在于,操作系统提供了这样的系统调用,使得原来的 while 循环里多次系统调用变成了一次系统调用 + 内核层遍历这些文件描述符。

epoll是现在最先进的IO多路复用器,Redis、Nginx,linux中的Java NIO都使用的是epoll。
1、一个socket的生命周期中只有一次从用户态拷贝到内核态的过程,开销小。
2、使用event事件通知机制,每次socket中有数据会主动通知内核,并加入到就绪链表中,不需要遍历所有的socket。

Reactor模式

单Reactor单线程

image.png

  • Reactor对象通过select监控客户端请求事件,收到事件后通过Dispatch进行分发。
  • 如果是建立连接的请求,就由Acceptor通过Accept处理连接请求,然后创建一个Handler对象处理连接完成后的业务处理。
  • 如果不是连接,则Reactor会分发调用连接对应的handler来响应。

优点:

  • 简单,没有多线程、进程通信、竞争问题

缺点:

  • 无法发挥多核CPU的性能。Handler在处理某个连接的业务时,整个进程无法处理其他连接时间,导致性能瓶颈。
  • 线程终止或者死循环,会导致整个系统的通信模块不可用,不能接收和处理外部信息,造成节点故障。

Redis采用这个模型

单Reactor多线程

image.png

  • Reactor对象通过select监控客户端请求事件,收到事件后通过Dispatch进行分发。
  • 如果是建立连接的请求,就由Acceptor通过Accept处理连接请求,然后创建一个Handler对象处理连接完成后的响应事件。
  • 如果不是连接,则Reactor会分发调用连接对应的handler来响应。
  • handler通过read读取数据后,不处理,而是分发给后续的worker线程池的某个线程处理业务。
  • worker线程池的某个线程完成业务后,将结果返回给handler。
  • handler收到响应后,通过send将结果返回给client。

优点

  • 充分利用多核CPU能力

缺点

  • 多线程数据共享和访问比较复杂,reactor处理所有事件的监听和响应,在单线程运行,在高并发场景容易出现性能瓶颈。

主从Reactor多线程

image.png

  • 主线程通过select监听连接事件,收到后,通过Acceptor处理连接事件。
  • Acceptor处理连接事件完成后,主线程将连接分配给SubReactor。
  • SubReactor会创建handler处理read请求,,不处理,而是分发给后续的worker线程池的某个线程处理业务。
  • worker线程池的某个线程完成业务后,将结果返回给handler。
  • handler收到响应后,通过send将结果返回给client。
  • 主线程可以对应多个子线程。

Netty模型

image.png

零拷贝

传统 IO 问题

传统的 IO 将一个文件通过 socket 写出
内部工作流程是这样的:

  1. java 本身并不具备 IO 读写能力,因此 read 方法调用后,要从 java 程序的用户态切换至内核态,去调用操作系统(Kernel)的读能力,将数据读入内核缓冲区。这期间用户线程阻塞,操作系统使用 DMA(Direct Memory Access)来实现文件读,其间也不会使用 cpu

DMA 也可以理解为硬件单元,用来解放 cpu 完成文件 IO

  1. 内核态切换回用户态,将数据从内核缓冲区读入用户缓冲区(即 byte[] buf),这期间 cpu 会参与拷贝,无法利用 DMA
  2. 调用 write 方法,这时将数据从用户缓冲区(byte[] buf)写入 socket 缓冲区,cpu 会参与拷贝
  3. 接下来要向网卡写数据,这项能力 java 又不具备,因此又得从用户态切换至内核态,调用操作系统的写能力,使用 DMA 将 socket 缓冲区的数据写入网卡,不会使用 cpu

可以看到中间环节较多,java 的 IO 实际不是物理设备级别的读写,而是缓存的复制,底层的真正读写是操作系统来完成的

  • 用户态与内核态的切换发生了 3 次,这个操作比较重量级
  • 数据拷贝了共 4 次

NIO 优化

通过 DirectByteBuf

  • ByteBuffer.allocate(10) HeapByteBuffer 使用的还是 java 内存
  • ByteBuffer.allocateDirect(10) DirectByteBuffer 使用的是操作系统内存


大部分步骤与优化前相同,不再赘述。唯有一点:java 可以使用 DirectByteBuf 将堆外内存映射到 jvm 内存中来直接访问使用

  • 这块内存不受 jvm 垃圾回收的影响,因此内存地址固定,有助于 IO 读写
  • java 中的 DirectByteBuf 对象仅维护了此内存的虚引用,内存回收分成两步
    • DirectByteBuf 对象被垃圾回收,将虚引用加入引用队列
    • 通过专门线程访问引用队列,根据虚引用释放堆外内存
  • 减少了一次数据拷贝,用户态与内核态的切换次数没有减少

进一步优化(底层采用了 linux 2.1 后提供的 sendFile 方法),java 中对应着两个 channel 调用 transferTo/transferFrom 方法拷贝数据

  1. java 调用 transferTo 方法后,要从 java 程序的用户态切换至内核态,使用 DMA将数据读入内核缓冲区,不会使用 cpu
  2. 数据从内核缓冲区传输到 socket 缓冲区,cpu 会参与拷贝
  3. 最后使用 DMA 将 socket 缓冲区的数据写入网卡,不会使用 cpu

可以看到

  • 只发生了一次用户态与内核态的切换
  • 数据拷贝了 3 次

进一步优化(linux 2.4)

  1. java 调用 transferTo 方法后,要从 java 程序的用户态切换至内核态,使用 DMA将数据读入内核缓冲区,不会使用 cpu
  2. 只会将一些 offset 和 length 信息拷入 socket 缓冲区,几乎无消耗
  3. 使用 DMA 将 内核缓冲区的数据写入网卡,不会使用 cpu

整个过程仅只发生了一次用户态与内核态的切换,数据拷贝了 2 次。所谓的【零拷贝】,并不是真正无拷贝,而是在不会拷贝重复数据到 jvm 内存中,零拷贝的优点有

  • 更少的用户态与内核态的切换
  • 不利用 cpu 计算,减少 cpu 缓存伪共享
  • 零拷贝适合小文件传输

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

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

相关文章

linux docker安装 gitlab后忘记root密码如何找回

1. docker ps - a 查看当前gitlab 当前的id2. docker exec -it gitlab /bin/bash 进入docker git 容器中【gitlab 注意可以上图中的name&#xff0c;也可以是id都可以的】,如下图3.gitlab-rails console -e production 输入该指令&#xff0c;启动Ruby on Rails控制台&…

Copy as cURL 字段含义

当前端在开发过程中&#xff0c;遇到接口错误反馈给后端人员时&#xff0c;一般在此接口处右键复制为cURL。 格式如下&#xff1a; curl https://xxx.xxx.cn/xxx/xxx/management/record/list \-H accept: application/json, text/plain, */* \-H accept-language: zh-CN,zh;q0…

LVS集群中的负载均衡技术

目录 1、LVS技术原理 2、NAT模式原理及部署方法 1、工作原理 2、部署方法 1、网络配置 2、软件安装与启用 3、测试 2、DR模式工作原理及部署方法 1、工作原理 2、部署方法 1、网络配置 2、解决vip响应问题 3、测试 3、ipvsadm命令及参数 1.管理集群服务中的增删…

数据同步工具之Flink CDC

Flink CDC&#xff08;Change Data Capture&#xff09;是基于Apache Flink的一个扩展&#xff0c;用于捕获和处理数据库中的数据变化。它能够实时捕获关系数据库中的数据变更&#xff08;如插入、更新、删除操作&#xff09;&#xff0c;并将这些变更流式传输到Flink进行处理。…

【OceanBase系列】—— OceanBase应急三板斧

作者&#xff1a; 花名&#xff1a;洪波&#xff0c; OceanBase 数据库解决方案架构师 目前随着OceanBase数据库越来越流行&#xff0c;社区已经有很多用户在生产环境使用了OceanBase&#xff0c;也有不少用户的核心业务用到了OceanBase数据库&#xff0c;在使用OceanBase数据库…

演示:基于WPF的DrawingVisual开发的矢量地图

一、目的&#xff1a;基于WPF的DrawingVisual开发的矢量地图 二、预览 默认样式 深黑样式 深蓝色样式 深蓝色透明样式 三、环境 VS2022&#xff0c;Net7&#xff0c;GDAL,审图号为GS(2019)1822号矢量数据,DrawingVisual 四、主要功能 支持多种显示样式&#xff08;默认样式&…

代码随想录27期|Python|Day39|​62. 不同路径​|​63. 不同路径 II​

62. 不同路径 简单题。由于规定了只能走右边和下边&#xff0c;所以右下角的值等于左对角线的两数之和。 1、确定dp和下标&#xff1a;二维数组&#xff0c;i&#xff0c;j分别为行和列&#xff0c;dp值为所需步数&#xff1b; 2、 初始化&#xff1a;只有上边和左边全部初始…

Java二十三种设计模式-享元模式(12/23)

享元模式&#xff1a;高效管理大量对象的设计模式 引言 在软件开发中&#xff0c;有时需要处理大量相似或重复的对象&#xff0c;这可能导致内存使用效率低下和性能问题。享元模式提供了一种解决方案&#xff0c;通过共享对象的共同部分来减少内存占用。 基础知识&#xff0c…

Apache OFBiz 曝出严重漏洞,允许预身份验证 RCE

近日&#xff0c;研究人员发现 Apache OFBiz 中存在一个新的关键漏洞&#xff0c;该漏洞是 Apache OFBiz 中的一个错误授权问题&#xff0c;被追踪为CVE-2024-38856。该漏洞影响 18.12.14 之前的版本&#xff0c;18.12.15 版本解决了该漏洞。 SonicWall 的安全研究员 Hasib Vh…

字节跳动发Seed-TTS语音合成模型,可模仿任意人的声音,效果逼真

前期我们介绍过很多语音合成的模型&#xff0c;比如ChatTTS&#xff0c;微软语音合成大模型等&#xff0c;随着大模型的不断进步&#xff0c;其合成的声音基本跟真人没有多大的区别。本期介绍的是字节跳动自家发布的语音合成模型Seed-TTS。 Seed-TTS 推理包含四个功能模块&…

JavaScript中判断变量的类型

数据类型 在 JavaScript 中有 8 种基本的数据类型&#xff08;7 种原始类型和 1 种引用类型&#xff09;&#xff0c;它们分别是&#xff1a; 原始类型/基本类型&#xff1a; Number&#xff0c;BigInt&#xff0c;String&#xff0c;Boolean&#xff0c;null&#xff0c;unde…

C++开发基础之深入理解C++中的两种单例模式实现——线程安全与效率的权衡

引言&#xff1a; 单例模式是设计模式中的一种&#xff0c;它保证一个类仅有一个实例&#xff0c;并提供一个全局访问点。在C中&#xff0c;实现单例模式的方式多种多样&#xff0c;但随着多线程应用的普及&#xff0c;如何确保单例模式在多线程环境下的线程安全性成为了一个重…

深度学习--图像分割UNet介绍及代码分析

UNet介绍 参考UNet网络介绍整体架构UNet过程输入编码器&#xff08;下采样&#xff09;中间特征表示解码器&#xff08;上采样&#xff09;输出 代码详解unetUP和Unet关系上采样模块——unetUp用于图像分割的卷积神经网络&#xff08;CNN&#xff09;架构模块——Unet类的定义初…

使用 Manim 创建一个二维坐标平面【NumberPlane】

NumberPlane 是 Manim 中用于创建一个二维坐标平面的类。它可以帮助用户在场景中可视化坐标轴、网格线以及其他数学概念。具体来说&#xff0c;它的功能包括&#xff1a; 坐标轴&#xff1a;NumberPlane 提供了 x 轴和 y 轴&#xff0c;通常是中心对称的&#xff0c;允许用户清…

深入探究Python反序列化漏洞:原理剖析与实战复现

在现代应用程序开发中&#xff0c;Python反序列化漏洞已成为一个备受关注的安全问题。反序列化是Python中用于将字节流转换回对象的过程&#xff0c;但如果没有妥善处理&#xff0c;攻击者可以通过精心构造的恶意数据&#xff0c;利用反序列化漏洞执行任意代码&#xff0c;进而…

前端day4-表单标签

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>day4-表单</title> </head> <body&g…

# 基于MongoDB实现商品管理系统(2)

基于MongoDB实现商品管理系统&#xff08;2&#xff09; 基于 mongodb 实现商品管理系统之准备工作 1、案例需求 这里使用的不是前端页面&#xff0c;而是控制台来完成的。 具体的需求如下所示&#xff1a; 运行 查询所有 通过id查询详情 添加 - 通过id删除 2、案例分析 程…

进程创建,进程消亡

虚拟地址&#xff1a;通过虚拟技术&#xff0c;将外部存储设备的一部分空间&#xff0c;划分给系统&#xff0c;作为在内存不足时临时用作数据缓存。当内存耗尽时&#xff0c;电脑就会自动调用硬盘来充当内存&#xff0c;以缓解内存的紧张。 练习: 编写一个代码实现,一个父…

OGG转MP3音频格式转换:6种免费音频转换器推荐

在如今的数字音乐时代&#xff0c;不同音频格式的兼容性问题常常让我们感到困扰。其中&#xff0c;OGG和MP3是两种常见的音频格式&#xff0c;但由于设备和平台的支持问题&#xff0c;我们经常需要将OGG转换为MP3格式。 本文将为您详细介绍OGG和MP3的区别&#xff0c;为什么需要…

Spring Boot集成protobuf快速入门Demo

1.什么是protobuf&#xff1f; Protobuf&#xff08;Protocol Buffers&#xff09;是由 Google 开发的一种轻量级、高效的数据交换格式&#xff0c;它被用于结构化数据的序列化、反序列化和传输。相比于 XML 和 JSON 等文本格式&#xff0c;Protobuf 具有更小的数据体积、更快…