java epoll网络编程

news2024/11/14 14:34:17

java epoll网络编程

从通信开始

  • 人类社会的发展离不开相互协作,一起围猎、抵御野兽,一起扛起锄头夯地、夯人,再到你与好兄弟之间征战峡谷。在这一切互相协作的背后,都离不开信息的传递,也就是通信。一群人聚在一起,想搞点事情,少不得统一个想法,不然,你往左,他往右,还未开始,便已结束。又或者你抬手想打个招呼,人家还以为你想扇他,率先一个巴掌甩过来,大业未成身先卒,呜呼!

  • 彼此之间的想法要达成一致,得有个媒介,能够基于此将心中所想传达出去,并准确无误地到达对方,而对方亦能通过你传过来的这段信息,准确的理解你所要表达的意思(这就是通信协议,有了协议,他才能知道你抬手是在示好,而不是要打他)。

  • 最开始,人类纯粹以声音和肢体动作作为通信的媒介。往后,部落发展壮大,物资逐渐丰富,多到你记不住,等到酋长过来问你还有多少头猪的时候,你脑门一拍:“哎呀,忘了”,赶紧过去数一遍,数到一半,发出疑问:“前面是几头猪来着”。为了克服记忆不准确,易忘这个缺点,伟大的你在绳子上打个结,一个结代表一头猪,这下就可以信心满满的给酋长汇报了。之后,人类在结绳的基础上,逐步演进,形成了一门门独特的语言与文字。

  • 近现代,人类的足迹已经遍布全球,以往的通信方式由于传播距离、速度有限,已不能满足人类日益狂野的内心,一场变革势在必行。19世纪,摩尔斯发明了第一台电报机,将人类语言转化成摩斯码,通过电流传送到接受方,接受方收到摩斯码后便可根据事先约定好的协议将其解码为能够理解的人类语言,如此,一场跨越整山海的爱情便可以萌芽了。20世纪,随着计算机的兴起,其数量不断攀升,本着团结就是力量的原则,这些计算机之间也得建立一套通信方式,这样才方便集中大量搞事情。本文研究的内容便是彼此独立的计算机是怎么高效的搞事情。

计算机之间的通信

  • 如今,计算机已经普遍存在于我们的日常生活中,我们可以通过安装在计算机上的各类软件与其他人交换信息,十分便捷。在这便捷的背后,不禁思考,计算机咋就这么牛呢,隔着大老远,还能将你的土味情话传出去。

  • 人与人之间交流少不了三种基本媒介

    • 空气——作为声音传播介质,将你声带的振动传播出去。
    • 耳朵——作为声音的采集装置,捕捉声波,并将声波转化为生物电传输到神经。
    • 大脑——作为声音的处理装置,接收到生物电后,进行分析处理和存储。
  • 同样,计算机之间的交流也需要三种媒介:

    • 网线——作为电信号或光信号的传输介质。
    • 网卡——作为信号的采集和发射装置,完成信号的采集和转换。
    • 主机——作为信号的处理装置,接收到来自网卡的信号后,进行存储。
  • 我们知道,计算机主机内的cpu控制着数据的处理,一旦开机后,就一直处于运转状态,十足的大忙人。那么,问题来了,当网卡采集数据完成后,如此繁忙的cpu怎么知道数据准备好了,并启动处理程序呢?难不成整天不干旁事,就盯着网卡,看有没有数据到来,要是这样,我保证你立马会把你的计算机给砸了,毕竟,不能让撩妹进程阻塞了工作进程。为了弄清楚计算机是如何在协调这两个进程的,就需要讲下计算机体系当中的一个名词——中断,也只有了解了这个概念,才能更深入的理解epoll原理。

中断

  • 目前,大部分的个人计算机都是交互式多任务处理系统,有很多程序跑在这上面,在使用者看来,仿佛这些程序是同时运行的,工作、摸鱼,丝毫不耽误。但实际上,cpu并不能施展孙猴子的分身术,一个cpu在某个瞬间只能处理某一个进程。那么,计算机是怎么提供这个“并行”假象的呢?(只考虑单核CPU计算机)

  • 计算机cpu的运算速度极快,利用这一特性,可以将所有可运行的进程放在一个工作队列中,同时给每个进程分配一个时间片,待进程的时间片耗费完,cpu就会通过进程调度算法从工作队列中取出另一个进程,继续运行。于是,在我们使用者看来,这些程序仿佛是在同时运行着。

  • 在这里插入图片描述

  • 每当进程的时间片用完,cpu是如何知晓的呢?这就轮到中断上场了,计算机内的定时器每隔一个周期(一个周期的大小等于一个时间片),就会触发一个时钟中断,cpu接收到该信号后,就会进入相应的中断处理程序,完成进程切换,如右图所示。

什么是中断

  • 中断可以看作是一种信号,当cpu接收到该信号后,就会停止当前运行的进程,转而去执行预先设置好的中断处理程序。好比在学生时代,当你正专心的神游太虚,突然被老师点名,不得不结束当前的遐想,并立刻站起来接受老师的灵魂拷问。在计算机系统中,中断不仅控制者进程间的轮转,平常我们通过鼠标、键盘与计算机进行交互,也是通过中断机制完成的,包括上文提到的计算机通信,cpu亦是通过中断机制得知网卡接收到了数据。按照中断的产生来源,一般可将中断分为两类——硬中断软中断

硬中断

  • 硬中断是由外部设备产生的中断信号,如上文提到的时钟中断,以及我们常见的网卡、键盘、鼠标等,都会产生中断信号。在CPU的外部引脚上,有两根引脚是用来专门接收外部设备发出的中断信号的:
  • INTR引脚——可屏蔽中断:该中断并不会影响系统正常运行,比如来自外部设备如硬盘,打印机,网卡等。
  • NMI引脚——不可屏蔽中断:该中断一旦发生,就是发生比较严重的问题了,比如电源掉电,硬件线路故障等。
  • 当CPU接收到中断信号后,当前执行的进程会被打断,转而去执行中断处理程序,具体处理流程如下:
  • 1、当外部事件发生时,比如点击鼠标,就会产生一个高电平电信号,经过中断控制器(CPU只有一个IINTR引脚,中断控制器可使多个外部中断源共享该资源)传输到将INTR引脚。
  • 2、CPU在每条指令的最后一个时钟周期会去查询INTR引脚,若查询到中断请求信号有效,并且在开中断的情况下,会终止当前进程,将CPU寄存器的值保存到当前进程的栈空间中(保护上下文)。
  • 3、之后CPU会通过中断控制器获取中断号,再根据中断号查询中断向量表,获取中断处理程序的入口地址,进入中断处理程序。
  • 4、中断处理程序执行完毕,CPU恢复被打断进程的信息到寄存器中(恢复上下文),返回原来进程停止的位置继续执行。
  • 下图以网卡接受数据为例,展示了这一过程。
  • 在这里插入图片描述

软中断

  • cpu响应中断,会打断当前正在运行的进程,进入中断处理程序。与此同时,还会禁止中断,如果中断处理程序执行时间过长,会导致其它进程长时间得不到执行,系统实时响应能力差,甚至会导致中断丢失。因此,中断处理程序应该尽可能快的执行。
  • 在Linux系统中,为使硬中断的处理尽可能快。会将中断的处理分为上下两个部分,上半部分完成对硬件的即使响应,只处理一些紧急的事。而将耗费时间较长的事情,例如将网卡接受到的数据后,交给协议栈层层解析,将被推迟到下半部分完成。
  • 软中断便是实现下半部分处理的一种机制,在linux系统初始化时,会创建一组内核线程ksoftriqd,ksoftriqd的数量等于所在机器的cpu核数。ksoftriqd创建好后,会进入自己的循环体内,不断判断当前有没有软中断需要执行。有的话,就会进入对应的软中断处理函数,完成中断下半部分。
  • 我们还是以接收网卡数据为例,看看软中断是怎么工作的
    • 1、Linux初始化网络子系统时,通过open_softirq(NET_RX_SOFTIRQ, net_rx_action)注册接收数据网络包的软中断,同时会创建一个poll_list的数据结构,等待网卡驱动程序将其poll函数注册进来。
    • 2、网卡驱动初始化,往poll_list中添加自身的poll函数。
    • 3、网络数据到来,触发硬中断,在硬中断处理程序中完成一些基本设置后,调用raise_softirq(NET_RX_SOFTIRQ)触发软中断。
    • 4、ksoftriqd线程开始执行软中断号NET_RX_SOFTIRQ对应的软中断处理函数net_rx_action,net_rx_action会遍历poll_list,最终执行到网卡注册的poll函数,完成数据的接收和处理。
    • 在这里插入图片描述

系统调用

  • 计算机的各种资源是有限的(内存、cpu等),为了能够管理好这些资源,保证计算机安全高效的运行,计算机系统提供两种不同的运行级别——内核态和用户态。
  • 运行在用户态的进程不能直接访问操作系统内核数据结构和程序,此时,若是进程想要访问内核数据,只能通过系统调用的方式,将操作权限从内核态切换为内核态。系统调用就是操作系统向外暴露的一系列API的集合,应用程序通过调用这些API,就可以间接访问内核资源,如访问网卡接收到的数据或者通过网卡发送数据。
  • 系统调用是通过中断实现的,其处理过程与硬中断相似。不同的是,系统调用并不会通过INTR引脚触发中断,而是通过int指令实现。当用户态进程发起int 0x80指令时,cpu就会切换到内核态,查询中断向量表中128号中断的中断处理程序入口,执行system_call()系统调用。

epoll的演进

  • 有了上面的一些知识储备,我们对计算机之间的通信算是有了一个大致的了解,但上面讲述的都是一些基本原理,它们是怎么应用到实际的应用程序中去的呢?不妨让我们先看下两个应用程序之间的整体通信框架图,对其做个整体了解。

  • 在这里插入图片描述

  • 应用程序A想要给应用程序B发送数据,会先将数据发往sokcet缓冲区,然后再经过协议栈的层层封装,最终通过网卡发送出去。另一端的网卡接收到数据后,通过协议栈反向解析,最终到达socket缓冲区,并通知应用程序B的读取。

  • 在计算机系统中,要完成上述过程,便需要借助我们常见的五种IO模型:

    • 阻塞IO
    • 非阻塞IO
    • IO多路复用
    • 信号驱动IO
    • 异步IO
  • 本文主要讲的是epoll,属于IO多路复用模型。在这里,我会从最原始的阻塞IO出发,再深入到epoll的实现,通过两者的对比,希望各位看官能够对epoll的理解更加深入。

阻塞IO

  • 上文讲到应用程序想要获取内核中的数据,只能通过系统调用的方式。在linux内核中,就提供了这样的一组系统调用供应用程序使用。我们以阻塞IO为例,服务端与客户端的交互如下:

  • 在这里插入图片描述

    • 1、应用程序通过sokcet()在内核中创建socket文件,并返回该文件描述符,应用程序通过文件描述符就可以操作该socket。sokcet在内核里是一种复杂的数据结构,这里,我们只需了解其中的三种即可——接受缓冲区、发送缓冲区、等待队列。

    • 在这里插入图片描述

    • 2、socket创建好后,调用bind()将socket和本机的ip:port绑定起来,之后通过listen()设置socket为监听状态,紧接着调用accept()接收来自客户端的连接。accpet()调用会阻塞当前进程,直到客户端发送连接请求(此处的进程阻塞过程与下文将要提到的recv()相同)

    • 3、当客户端通过connect()发起连接请求,服务端内核在接收到该请求后,便会在内核中创建一个socket,并将其描述符返回给应用程序。内核创建的socket将与ip四元组——服务器IP地址、服务器端口,客户端IP地址、客户端端口绑定,当服务器有数据到达时,内核解析出数据中携带的ip地址,再与socket绑定的ip地址对比,就可以确定该数据要发往哪个socket的接收缓冲区。

    • 4、上述准备工作结束后,服务端即可通过recv()系统调用,接收客户端发来的数据。服务端发起recv()调用时,会陷入内核态,内核程序会首先检查socket的接收缓冲区是否有数据,如果有数据,则直接读取,并返回给应用程序。否则,内核会将当前进程移除【就绪队列】,并创建一个等待队列项,将当前进程的描述符添加到等待队列项中,与此同时,还会在等待队列项中注册回调函数autoremove_wake_function()。如此,CPU在收到时钟中断,执行调度程序时就不会调入该进程,从而阻塞该进程。

    • 在这里插入图片描述

    • 5、客户端通过send()将数据发送出去,服务端的网卡接收到数据后,发出中断信号。CPU开始执行中断处理,在中断处理的下部分调用网卡驱动注册的poll()函数,将内核ringBuffer中的数据取出来,并组装为sk_buffer结构体(sk_buffer是linux网络代码中重要的数据结构,负责管理和控制接收或发送数据包的信息)。之后交由协议栈做反向解析,将解析出的数据送到sokcet的接收缓冲区。最后执行在上一步注册在socket等待队列项中的回调函数autoremove_wake_function(),唤醒进程A。

    • 6、进程A从中断处继续执行,进行数据处理。至此,一次完整的通信宣告结束。

    • 在这里插入图片描述

多路复用——select、poll、epoll

  • 阻塞IO只适用于少量连接的情况,如果服务端需要处理上百万的客户端连接,阻塞IO的性能将变的极差,主要因为以下几点:
    • recv()会导致一次系统调用,在没有数据到来的时候,进程会被阻塞,导致一次进程切换(进程切换是一件相对耗时的操作)。
    • 当有数据到来时,触发一次中断,唤醒进程,又导致一次进程切换。
    • 一个recv()只能处理一个连接,当需要处理多个连接时,需要开启多个线程,但是每个线程都是需要占用一定的系统资源的。大量线程会使系统资源很快被消耗完,系统性能急剧下降。
  • 为高效处理大量客户端连接,IO多路复用技术便登上历史的舞台。IO多路复用就是利用一个用户态进程或线程管理多个连接,以此来减少频繁的进程切换和线程创建。在Linux中,其实现方式有三种,分别是select、poll、epoll。
  • 在这里插入图片描述
select
  • Linux中,select是最早实现IO多路复用的。其思想也较为简单,就是通过轮训的方式来管理若干个连接。主要流程如下:

    • 1、将需要管理的socket连接全部放到一个文件描述符号集合fds,然后通过调用select()将fds拷贝到内核中。
    • 2、内核遍历fds,检查socket接收缓冲区是否有数据,有的话就将此socket标记为可读,遍历结束后把整个fds拷贝回用户进程,用户进程再遍历fds,找到被标记的fds,然后处理数据;如果所有的socket接收缓冲区都没有数据,就会将进程描述符加入到每个socket中的等待队列项中,并将当前进程从内核的就绪队列中移除,阻塞该进程。
    • 3、当任何一个socket接收缓冲区接收到数据,都会标记自身为可读状态,并唤醒被阻塞的用户进程,返回fds。之后用户进程遍历fds,找到被标记的fds,进行数据处理。
    • 在这里插入图片描述
  • 从上述的流程来看,select实现虽然简单,但是却有以下缺点,导致性能不佳:

    • 1、每次select都需要把fds拷贝到内核态,内核态遍历fds。
    • 2、有数据到来时,需要将fds从内核拷贝回用户空间,用户进程再遍历fds。
    • 3、selec只支持同时管理1024个连接,有内核中的FD_SETSIZE宏定义限制。
poll
  • poll 和 select 本质上没有太大区别,只不过poll用的不是数组来存储文件描述符,而是以链表的形式存储,不会受到1024大小的限制。但还是需要在用户态和内核态之间来回拷贝fds,同时遍历文件描述符集合来找到可读 socket,性能损耗较大。
epoll
  • select/poll效率低主要有两个原因:其一是select/poll将维护文件描述符fds和阻塞进程这两件事情都放在来一个系统调用内完成,导致每次系统调用时,都需要将fds拷贝到内核中。其二是当fds中的某个socket有数据到来时,用户程序并不知道是哪个socket,只能通过遍历的方式获取数据。

  • 针对以上两点,epoll分别从两个方面着手,很好的解决了select/poll的问题。

  • 1、考虑到在实际应用中,fds中的socket都比较稳定,不会频繁发生变化。epoll通过epoll_create()在内核中创建一个eventepoll内核对象,eventepoll中有一个红黑树rbr,用来管理所有socket描述符,每个红黑树节点都是一个epitem对象,epitem对象中包含socket描述符。用户进程可以利用epoll_clt()函数实现对socket描述符的增删操作,时间复杂度为O(log n)。如此就不需要像select/poll样,每次调用都需要往内核中拷贝整个文件描述符集合。

  • 2、eventepoll对象中还有一个就绪链表rdlist,当rbr中的某个socket接收到数据时,就会将其对应的epitem加入rdlist中,并将rdlist返回给用户进程。如此就避免了select/poll所必须的遍历操作。

  • 在这里插入图片描述

  • 上面只介绍了epoll的一个整体思想,没有设计实现细节。接下来,我们就通过与前面阻塞IO的对比,探讨下epoll具体是怎么实现的。

  • 1、服务端进程通过epoll_create()在内核中创建了一个eventepoll内核对象,该对象中主要有三个数据结构:

    • 等待队列(wa):存放被阻塞进程的进程描述符,数据就绪的时会通过 wq ,找到被阻塞的进程。
    • 红黑树(rbr):通过红黑树管理服务端进程下添加进来的所有 socket 连接。
    • 就绪链表(rdlist):当有socke中接收到数据时,就会把其放到rdlist中,并拷贝给服务端进程。
  • 2、服务端进程收到sokcet连接后,通过epoll_ctl()将该socket转换为红黑树的epitem节点,插入到rbr中。同时会设置socket等待队列中等待项的回调函数为ep_poll_callback(),并使其持有epitem节点的引用。

  • 3、服务端调用epol_wait(),进入内核态,系统调用程序检查rdlist是否为空,不为空则直接拷贝rdlist回服务端进程空间,否则阻塞服务端进程,等待数据到来。

  • 4、客户端发送数据,服务端收到后,开启中断处理,最终会调用第二步注册的ep_poll_callback()函数,将该socket转换为红黑树的epitem节点,添加到rdlist中,然后拷贝rdlist到服务端进程空间。

  • 5、最后并唤醒服务端进程,进行数据处理。

  • 最后,我们以一张图来描述整个epoll的过程,其中灰色部分的处理与阻塞IO相同,彩色部分为epoll特有,通过与阻塞IO的对比,我们能够更好的理解epoll的底层原理。

  • 在这里插入图片描述

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

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

相关文章

Android学习笔记(一) Android Studio 安装配置

大家好,我是半虹,这篇文章来讲 Android Studio 的安装配置 1、基本介绍 Android Studio 是 Google 推出的 Android 集成开发环境,可以用于创建、开发和调试 Android 项目 Android Studio 是基于 IntelliJ IDEA 开发的 ,提供了专…

typora的一些配置方法

1.设置图片的保存路径 选择 文件 --> 偏好设置 --> 图像 --> 设置图片要保存的位置 将网络的图片保存到本地,例如,从csdn复制文章时,将文章中的图片自动保存到本地 在图像设置的插入图片时一栏,勾选对网络位置的图片应…

vmware中克隆过来的linux节点无system eth0

问题现象 使用vmware虚拟机的克隆功能后,找不到system eth0 解决办法 编辑/etc/udev/rules.d/70-persistent-net.rules文件 可以看到,eth0,是克隆前机器的网卡,eth1是克隆后机器生成的网卡,所以把NAME"eth0&q…

解决方案:在autodl环境下安装torch被killed掉

文章目录 一、现象二、解决方案 一、现象 平台:autodl 镜像:PyTorch 2.0.0 Python 3.8(ubuntu20.04) Cuda 11.8 GPU:A40(48GB) * 1 CPU:15 vCPU AMD EPYC 7543 32-Core Processor 内存:80GB 安装torch:1.13.0环境&a…

基于CloudflareSpeedTest项目实现git clone加速

1.网络测速 「自选优选 IP」测试 Cloudflare CDN 延迟和速度,获取最快 IP 更多内容参考项目:https://github.com/XIU2/CloudflareSpeedTest 国外很多网站都在使用 Cloudflare CDN,但分配给中国内地访客的 IP 并不友好(延迟高、丢…

串口助手使用和插入usb转TTL的COM口识别问题

问题出现原因 由于串口调试中经常需要通过断电对单片机烧录程序,所以制作了一个转接带开关的USB 转接口,如下图所示,其中按键控制的是OUT口的电源通断。但为了能够数据传输,有两根传输数据的线是一直连接的。在使用usb进行程序烧…

【Threejs学习】创建Threejs页面

学习文档地址: threejs官网:https://threejs.org/ Threejs官网中文文档:https://threejs.org/docs/index.html#manual/zh/ threejs中文网:http://www.webgl3d.cn/ threejs基础教程:http://www.webgl3d.cn/pages/aac9ab…

《软件工程导论》(第6版)第6章 详细设计 复习笔记

第6章 详细设计 一、详细设计概述 1.目标 (1)详细设计阶段的根本目标是确定应该怎样具体地实现所要求的系统,即经过这个阶段的设计工作,应该得出对目标系统的精确描述,从而在编码阶段可以把这个描述直接…

四足机器人控制算法——建模、控制与实践(unitree_guide配置)

目录 官方文档 unitree_guide 1. 快捷指令 2. ROS安装 3. LCM库安装 3.1. 安装步骤 4. pthread库 5. 工程文件下载 6. 编译 报错: 报错1 报错2: 报错3 其他报错 7. 运行 7.1. 运行 Gazebo 仿真环境 7.2. 启动控制器 8. 简单使用 官方文…

贪心(临项交换)

前言&#xff1a;对待这个问题&#xff0c;我想到就是一定是贪心&#xff0c;但是我不知道怎么排序 对待这种问题&#xff0c;肯定是要先假设只有两个&#xff0c;我们要怎么排序呢 class Solution { public:long long minDamage(int power, vector<int>& damage, v…

分布式主键

目录 1.分布式主键的基本需求 2.常见的分布式主键生成策略 2.1UUID&#xff08;128位&#xff09; 2.2MySQL 2.2.1自增主键 2.2.2区间号段 2.3Redis 2.4SnowFlake雪花算法&#xff08;64位&#xff09; 1.分布式主键的基本需求 全局唯一&#xff1a;不管什么主键&…

Linux驱动(二):模块化编程的基本操作

目录 前言一、模块化编程1.模块化驱动代码框架2.printk详解3.应用操作 二、多模块编程三、多文件编程四、函数传参 前言 没多少东西&#xff0c;就是最基础的一些Linux驱动编写操作。 一、模块化编程 驱动加载到内核中的两种方法&#xff1a; 1.静态编译&#xff1a;就是将模…

【Python百日进阶-Web开发-Feffery】Day500 - dash使用秘籍

文章目录 前言:fac是什么?“人生苦短,我用Python;Web开发,首选Feffery!”↓↓↓ 今日笔记 ↓↓↓1 dash应用使用cdn加载静态资源1.1 页面效果1.2 项目源码2 suppress_callback_exceptions=True3 阻止首次回调3.1 阻止所有回调函数的首次回调3.2 阻止单个回调函数的首次回…

《JavaEE进阶》----5.<SpringMVC②剩余基本操作(CookieSessionHeader响应)>

Cookie和Session简介。 Spring MVC的 2.请求 Cookie的设置和两种获取方式 Session的设置和三种获取方式。 3.响应 1.返回静态页面 2.返回数据 3.返回HTML片段 4.返回JSON 5.设置状态码 6.设置header 三、&#xff08;接上文&#xff09;SpringMVC剩余基本操作 3.2postman请求 …

CSAPP Data Lab

CSAPP 的第一个 Lab&#xff0c;对应知识点为书中的第 2 章&#xff08;信息的表示与处理&#xff09;&#xff0c;要求使用受限制的运算符和表达式实现一些位操作。主要分为两个部分&#xff1a;整数部分和浮点数部分。其中整数部分限制较多&#xff0c;比较偏重技巧性&#x…

Red Hat Enterprise Linux 9—Red Hat 9.4Linux系统 Mac电脑虚拟机安装【保姆级教程】

Mac分享吧 文章目录 效果一、下载软件二、安装软件与配置1、安装2、配置 三、查看基本信息安装完成&#xff01;&#xff01;&#xff01; 效果 一、下载软件 下载软件 地址&#xff1a;www.macfxb.cn 二、安装软件与配置 1、安装 2、配置 三、查看基本信息 安装完成&#xf…

【国考】特值法

特值法 题干中存在乘除关系&#xff0c;且对应量未知。 例3&#xff1a;甲、乙、丙三个工程队的效率比为6&#xff1a;5&#xff1a;4,现将A、B两项工作量相同的工程交给这三个工程队,甲队负责A工程,乙队负责B工程,丙队参与A工程若干天后转而参与B工程.两项工程同时开工,耗时16…

【PyQt6 应用程序】视频百叶窗效果一键生成模块

在现代的多媒体创作中,音频和视频的结合是提升作品感染力的关键因素之一。尤其是短视频的制作,往往需要根据音频的节奏进行精细的剪辑和特效添加。PyQt6 作为一个功能强大的 Python GUI 库,为我们提供了极大的便利,使得我们可以轻松地创建功能丰富的应用程序。 本教程将一…

J.U.C并发工具集实战及原理分析

​在J.U.C里提供了很多的并发控制工具类&#xff0c;这些工具类可以使得线程按照业务的某种约束来执行。本节包含CountDownLatch、Semaphore、CyclicBarrier等工具类。目的是了解他们基本使用、原理及实际应用。 1. CountDownLatch主题 1.1 CountDownLatch简介 CountDownLat…

ShardingSphere-JDBC实现数据加解密

一、什么是ShardingSphere&#xff1f; ShardingSphere定位为轻量级 Java 框架&#xff0c;在 Java 的 JDBC 层提供的额外服务。 它使用客户端直连数据库&#xff0c;以 jar 包形式提供服务&#xff0c;无需额外部署和依赖&#xff0c;可理解为增强版的 JDBC 驱动&#xff0c;完…