【Nginx11】Nginx学习:HTTP核心模块(八)文件处理
Nginx学习:HTTP核心模块(八)文件处理
继续我们的 HTTP 核心模块之旅。今天主要是文件相关的一些处理操作,包括 DirectIO、文件缓存以及 sendfile 相关的配置。这三个配置中,大家应该会见过 sendfile ,但是另外两个就比较少见了。包括我之前也从来没见过,不过还好,DirectIO 并不是一个完全的陌生人,文件缓存优化也与操作系统基础知识有关,而 sendfile 一般默认就是开启的,所以大家也不要有太大的压力哦。
directio
是不是看着很眼熟,没错,早前我们在 PHP 的小课堂文章中学习过。没印象或者想不起来的小伙伴可以移步 PHP中DirectIO直操作文件扩展的使用https://mp.weixin.qq.com/s/fS6X2IlrnrBrBZwwqRF3vA (去博客搜索也可以哦) 去回忆一下哦。别的不多说了,直接来看看相关的配置。由于不知道要怎么测试,所以就简单地介绍一些这些配置就好了,如果有小伙伴了解这一块要怎么测试的,可以评论区留言哦。
directio
当读入长度大于等于指定 size 的文件时,开启 DirectIO 功能。
directio size | off;
具体的做法是, 在 FreeBSD 或 Linux 系统开启使用 O_DIRECT 标志, 在 Mac OS X 系统开启使用 F_NOCACHE 标志, 在 Solaris 系统开启使用 directio() 功能。这条指令自动关闭 sendfile(0.7.15版) 。它在处理大文件时 directio 4m; 或者在 Linux 系统使用 aio 时比较有用。默认 off 。
directio_alignment
为 DirectIO 设置文件偏移量对齐。
directio_alignment size;
大多数情况下,按512字节对齐足矣, 但在 Linux 系统下使用 XFS ,需要将值扩大到 4K 。
文件优化缓存
这个缓存是个什么东西呢?它可以用于减少 Nginx 的系统调用,缓存文件句柄、大小和修改时间等。具体作用我们在最后会看到。
open_file_cache
用于配置文件缓存。
open_file_cache off;
open_file_cache max=N [inactive=time];
默认是 off ,也就是关闭状态的。可配置的值包括:
max=N,设置缓存中元素的最大数量,当缓存溢出时,使用 LRU(最近最少使用) 算法删除缓存中的元素
inactive=time,在这段时间内缓存元素如果没有被访问,将从缓存中删除。默认超时是60秒
它可以缓存的内容包括:
打开文件的描述符,大小和修改时间
目录查找结果
文件查找时的错误结果,诸如“file not found”(文件不存在)、“no read permission”(无读权限) 等等
在使用文件缓存的时候,最好 open_file_cache_errors 也开启,这个命令我们后面马上会说。
open_file_cache_errors
开启或者关闭缓存文件查找的错误结果
open_file_cache_errors on | off;
默认值是 off ,如果确定要使用文件缓存的话,最好把它也打开。
open_file_cache_min_uses
设置在由 open_file_cache 指令的 inactive 参数配置的超时时间内, 文件应该被访问的最小 number(次数) 。
open_file_cache_min_uses number;
如果访问次数大于等于此值,文件描述符会保留在缓存中,否则从缓存中删除。
open_file_cache_valid
设置检查 open_file_cache 缓存的元素的时间间隔。
open_file_cache_valid time;
默认 60s 检查一次。
配置测试
首先使用 strace 命令追踪一下当前 Nginx 的 Worker 进程,为了方便测试,咱们可以把 worker_processes 设置为 1 ,只启动一个工作进程。然后随便请求一个 URL 就会出现下面的内容。
[root@localhost html]# strace -p 2365
strace: Process 2365 attached
epoll_wait(15, [{EPOLLIN, {u32=2915635216, u64=140495590854672}}], 512, -1) = 1
accept4(8, {sa_family=AF_INET, sin_port=htons(54245), sin_addr=inet_addr("192.168.56.1")}, [112->16], SOCK_NONBLOCK) = 5
epoll_ctl(15, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLRDHUP|EPOLLET, {u32=2915635912, u64=140495590855368}}) = 0
epoll_wait(15, [{EPOLLIN, {u32=2915635912, u64=140495590855368}}], 512, 60000) = 1
recvfrom(5, "GET /aaa HTTP/1.1\r\nTEST_UNDERLIN"..., 1024, 0, NULL, NULL) = 224
// =======注意这里=======
openat(AT_FDCWD, "/usr/local/nginx/html/aaa", O_RDONLY|O_NONBLOCK) = 6
fstat(6, {st_mode=S_IFDIR|0755, st_size=24, ...}) = 0
close(6) = 0
// =======注意这里=======
writev(5, [{iov_base="HTTP/1.1 301 Moved Permanently\r\n"..., iov_len=200}, {iov_base="<html>\r\n<head><title>301 Moved P"..., iov_len=116}, {iov_base="<hr><center>nginx/1.23.0</center"..., iov_len=53}], 3) = 369
write(7, "192.168.56.1 - - [07/Aug/2022:22"..., 102) = 102
setsockopt(5, SOL_TCP, TCP_NODELAY, [1], 4) = 0
epoll_wait(15, [{EPOLLIN, {u32=2915635912, u64=140495590855368}}], 512, 65000) = 1
recvfrom(5, "GET /aaa/ HTTP/1.1\r\nTEST_UNDERLI"..., 1024, 0, NULL, NULL) = 260
stat("/usr/local/nginx/html/aaa/index.html", {st_mode=S_IFREG|0644, st_size=10, ...}) = 0
// =======注意这里=======
openat(AT_FDCWD, "/usr/local/nginx/html/aaa/index.html", O_RDONLY|O_NONBLOCK) = 6
fstat(6, {st_mode=S_IFREG|0644, st_size=10, ...}) = 0
// =======注意这里=======
writev(5, [{iov_base="HTTP/1.1 200 OK\r\nServer: nginx/1"..., iov_len=235}], 1) = 235
sendfile(5, 6, [0] => [10], 10) = 10
write(7, "192.168.56.1 - - [07/Aug/2022:22"..., 125) = 125
close(6) = 0
epoll_wait(15,
主要需要看的就是上面注释中的部分,有三个操作,分别是 openat、fstat 和 close 操作,分别对应着打开文件句柄、获取文件统计信息以及关闭文件句柄这三个操作。目前的情况下,多次访问,还是一样的结果,每次都会有这三个步骤。
接下来,我们就简单配置下文件缓存,直接使用官方文档中提供的示例配置。这几个命令可以配置在 http、server、location 中,我们就简单地在 server 中进行配置吧。
server {...open_file_cache max=1000 inactive=20s;open_file_cache_valid 30s;open_file_cache_min_uses 2;open_file_cache_errors on;...
}
然后再次访问,第一次还是会正常出现 openat、fstat 和 close 这三个系统函数的调用。但是之后再次访问,就会发现这三个系统调用不见了。
[root@localhost html]# strace -p 2423
strace: Process 2423 attached
epoll_wait(15, [{EPOLLIN, {u32=2915635216, u64=140495590854672}}], 512, -1) = 1
accept4(8, {sa_family=AF_INET, sin_port=htons(65255), sin_addr=inet_addr("192.168.56.1")}, [112->16], SOCK_NONBLOCK) = 7
epoll_ctl(15, EPOLL_CTL_ADD, 7, {EPOLLIN|EPOLLRDHUP|EPOLLET, {u32=2915635912, u64=140495590855368}}) = 0
epoll_wait(15, [{EPOLLIN, {u32=2915635912, u64=140495590855368}}], 512, 60000) = 1
recvfrom(7, "GET /aaa HTTP/1.1\r\nTEST_UNDERLIN"..., 1024, 0, NULL, NULL) = 224
// =======注意这里=======
openat(AT_FDCWD, "/usr/local/nginx/html/aaa", O_RDONLY|O_NONBLOCK) = 12
fstat(12, {st_mode=S_IFDIR|0755, st_size=24, ...}) = 0
close(12) = 0
// =======注意这里=======
writev(7, [{iov_base="HTTP/1.1 301 Moved Permanently\r\n"..., iov_len=200}, {iov_base="<html>\r\n<head><title>301 Moved P"..., iov_len=116}, {iov_base="<hr><center>nginx/1.23.0</center"..., iov_len=53}], 3) = 369
write(5, "192.168.56.1 - - [07/Aug/2022:23"..., 102) = 102
setsockopt(7, SOL_TCP, TCP_NODELAY, [1], 4) = 0
epoll_wait(15, [{EPOLLIN, {u32=2915635912, u64=140495590855368}}], 512, 65000) = 1
recvfrom(7, "GET /aaa/ HTTP/1.1\r\nTEST_UNDERLI"..., 1024, 0, NULL, NULL) = 260
// =======注意这里=======
openat(AT_FDCWD, "/usr/local/nginx/html/aaa/index.html", O_RDONLY|O_NONBLOCK) = 12
fstat(12, {st_mode=S_IFREG|0644, st_size=10, ...}) = 0
// =======注意这里=======
writev(7, [{iov_base="HTTP/1.1 200 OK\r\nServer: nginx/1"..., iov_len=235}], 1) = 235
sendfile(7, 12, [0] => [10], 10) = 10
write(5, "192.168.56.1 - - [07/Aug/2022:23"..., 125) = 125
// 这里开始是后续访问
epoll_wait(15, [{EPOLLIN, {u32=2915635912, u64=140495590855368}}], 512, 65000) = 1
recvfrom(7, "GET /aaa HTTP/1.1\r\nTEST_UNDERLIN"..., 1024, 0, NULL, NULL) = 224
writev(7, [{iov_base="HTTP/1.1 301 Moved Permanently\r\n"..., iov_len=200}, {iov_base="<html>\r\n<head><title>301 Moved P"..., iov_len=116}, {iov_base="<hr><center>nginx/1.23.0</center"..., iov_len=53}], 3) = 369
write(5, "192.168.56.1 - - [07/Aug/2022:23"..., 102) = 102
epoll_wait(15, [{EPOLLIN, {u32=2915635912, u64=140495590855368}}], 512, 65000) = 1
recvfrom(7, "GET /aaa/ HTTP/1.1\r\nTEST_UNDERLI"..., 1024, 0, NULL, NULL) = 260
writev(7, [{iov_base="HTTP/1.1 200 OK\r\nServer: nginx/1"..., iov_len=235}], 1) = 235
sendfile(7, 12, [0] => [10], 10) = 10
write(5, "192.168.56.1 - - [07/Aug/2022:23"..., 125) = 125
epoll_wait(15,
很明显,这就是文件缓存在起作用。减少了文件相关的系统调用读取的次数。为什么我们上面访问的内容会有两遍请求呢?我访问的是 /aaa 目录,直接访问目录会找这个目录下面的 index.html 文件,因此有一次 301 跳转。你也可以直接访问 /aaa/index.html 就会看得更清楚一些了。
这一套文件缓存不会缓存文件的具体内容,而只是操作符句柄及文件的一些统计信息,Nginx 虽然已经对静态内容做过优化。但在高流量网站的情况下,仍然可以使用 open_file_cache 进一步提高性能,减少系统调用。通过扩大这个缓存的容量可以提高线上的实际命中率。但是缓存容量并不是越大越好,比如当达到 20000 个元素的容量时,共享内存的锁就成了瓶颈。所以,可以在确定访问频次非常高的静态文件 location 或者 server 上开启这一套文件缓存,数量也不用太多,可以让性能有更进一步的提升。
PHP-FPM 或者反向代理之类的和这个文件缓存就没啥关系了,PHP-FPM 走的是 socket 句柄,通过连接 PHP-FPM 进行操作,而打开 php 文件的是 PHP-FPM ,不是 Nginx 。
sendfile
这一块又要牵涉到操作系统了。在操作系统编程中,sendfile() 是系统调用,而 read() 这种是函数调用,因此,使用 sendfile() 对于文件读取来说会带来性能的提升。
读取发送文件的时候,使用了 sendfile() 那么 Nginx 就会直接向系统内核发送指令,然后发送文件也是系统内核直接完成,只有一次复制操作,实现了异步网络 IO 的形式。而传统情况则是从磁盘中以流的形式加载文件,然后再将文件流复制到系统内核中,内核再发送。区别就在这里。其实就是说,传统方式在读写文件时,会从硬盘到系统空间,再到用户空间这样走一圈,而使用 sendfile() 则只在系统空间,不用走到用户空间,从而实现零拷贝,减少空间切换的消耗。
当然,sendfile 只对静态网站有用,也就是确实需要进行文件读写发送的。如果是动态网站,比如 FastCGI 或反向代理的,对接的实际上是 socket 接口,真正的文件处理是在动态语言中进行的,比如 PHP 的模板文件加载等。因此,它只对静态页面或文件有性能提升的效果。
sendfile
开启或关闭使用 sendfile()
调用。
sendfile on | off;
现在默认就是打开的,从 nginx 0.8.12 和 FreeBSD 5.2.1 开始,可以使用 aio 预加载 sendfile的数据,Linux 没有哦。
send_lowat
如果设置成非 0 值,Nginx 将尝试最小化向客户端发送数据的次数。
send_lowat size;
默认值是 0 ,它是通过将 kqueue 方法的 NOTE_LOWAT 标志, 或者将套接字的 SO_SNDLOWA T属性设置成指定的 size 实现的。这条指令在Linux、Solaris和Windows操作系统无效。
sendfile_max_chunk
设置为非0值时,可以限制在一次 sendfile() 调用时传输的数据量。
sendfile_max_chunk size;
最新的版本默认 2m,如果不进行限制,一个快速的连接可能会霸占整个worker进程的所有资源。在版本1.21.4之前,默认情况下是 0 ,没有限制。
总结
好吧好吧,今天的内容有点水,因为我确实不知道 DirectIO 和 sendfile 在 Nginx 中要怎么测试。如果有了解过的小伙伴记得评论留言哦。不过通常来说,我们会在刚安装好的 Nginx 配置文件中看到 sendfile 是 on 的状态,一般来说,不懂的就别瞎配了,咱惹不起还躲不起嘛,其它的配置让它们就走默认好了。还是那句话,留个印象,将来如果有用到的时候,能够想起来并且查资料能有个方向就够了。毕竟,现在的水平只到这里,谁知道将来我们能不能成为大神呢,要是真成了,再回来好好补上呗!
参考文档:
http://nginx.org/en/docs/http/ngx_http_core_module.html
相关文章:
【Nginx11】Nginx学习:HTTP核心模块(八)文件处理
Nginx学习:HTTP核心模块(八)文件处理 继续我们的 HTTP 核心模块之旅。今天主要是文件相关的一些处理操作,包括 DirectIO、文件缓存以及 sendfile 相关的配置。这三个配置中,大家应该会见过 sendfile ,但是另…...

STM32MP157驱动开发——按键驱动(休眠与唤醒)
文章目录 “休眠-唤醒”机制:APP执行过程内核函数休眠函数唤醒函数 休眠与唤醒方式的按键驱动程序(stm32mp157)驱动程序框架button_test.cgpio_key_drv.cMakefile修改设备树文件编译测试 “休眠-唤醒”机制: 当应用程序必须等待某个事件发生,…...
全面解析 SOCKS5 代理与 HTTP 代理的对比与应用
一、 SOCKS5 代理与 HTTP 代理的基本原理 SOCKS5 代理:SOCKS5 是一种网络协议,它可以在传输层(Transport Layer)代理 TCP 和 UDP 请求。SOCKS5 代理不解析请求内容,而是直接将数据中转至目标服务器,支持更广…...
STM32 HEX文件和BIN文件格式区别keil中的配置与生成
一、区别 HEX 文件: 是包括地址信息的,在烧写或下载HEX文件的时候,一般都不需要用户指定地址,因为HEX文件内部的信息已经包括了地址。HEX文件是用ASCII来表示二进制的数值。例如一般8-BIT的二进制数值0x3F,用ASCII来表示就需要分别表示字符3和字符F,每个字符需要一个BYTE…...
RabbitMQ优先级队列的使用
RabbitMQ优先级队列的使用 生产者 public class PriorityQueue {public static void Send(){string path AppDomain.CurrentDomain.BaseDirectory;string tag path.Split(/, \\).Last(s > !string.IsNullOrEmpty(s));Console.WriteLine($"这里是 {tag} 启动了。。&…...

MAC 推送证书不受信任
配置推送证书的时候,一打开就变成不受信任,搜了很多解决版本。 由于苹果修改相关规定,推送证书 打开Apple PKI - Apple 下载AppleWWDRCA文件,选择G4,双击安装之后,证书已经变为受信任。 AppleWWDRCA(Apple Worldwid…...
Gitee创建分支
在使用Gitee进行代码托管时,分支是一个非常重要的概念。它可以让我们在不同的开发阶段、不同的团队成员之间协作开发,提高团队工作效率。因此,下面将介绍如何在Gitee仓库中建立分支。 一、在Gitee上创建新的分支 在讲解如何在Gitee上创建新…...

集群间ssh配置免密登录
ssh免密配置,可以将ssh生成的密钥分发给目标主机,之后再用ssh访问目标主机时就无需输入密码 下面我们来配置用centos71免密登录centos72主机 使用下面指令生成一个密钥 ssh-keygen其中会提示,是否输入密码短语,这里不输入&#…...

YOLOV8改进:CVPR 2023 | SCConv: 即插即用的空间和通道重建卷积
1.该文章属于YOLOV5/YOLOV7/YOLOV8改进专栏,包含大量的改进方式,主要以2023年的最新文章和2022年的文章提出改进方式。 2.提供更加详细的改进方法,如将注意力机制添加到网络的不同位置,便于做实验,也可以当做论文的创新点。 2.涨点效果:添加 SCConv,经过测试,有效涨点。…...

人员定位安全管控系统:提升安全管理水平的智能解决方案
在当今社会,人员安全管理成为各行各业关注的焦点。为了保障人员的安全和提高管理效率,人员定位安全管控系统应运而生。 人员定位安全管控系统采用多种定位技术来实现对人员位置的准确定位,如GPS(全球定位系统)、Wi-Fi…...

数据结构(二)
目录 Trie树 并查集 堆 Trie树 作用:用来高效地存储和查找字符串集合的数据结构 基本形式: 模板代码如下: #include<iostream> using namespace std;const int N 100010;//idx代表当前用到哪个下标 //既是根节点,又是空节点 //cnt存储的是以当前点结尾的…...
logback 自定义log字段(MDC)推送到logstash(spring boot + logback+ logstash)
直接上代码: 1.创建FIlter,往 MDC 里面追加内容 WebFilter Component public class LogBackFilter implements Filter {Overridepublic void init(FilterConfig filterConfig) throws ServletException {}Overridepublic void doFilter(ServletRequest…...
1253. 重构 2 行二进制矩阵
1253. 重构 2 行二进制矩阵 给你一个 2 行 n 列的二进制数组: 矩阵是一个二进制矩阵,这意味着矩阵中的每个元素不是 0 就是 1。第 0 行的元素之和为 upper。第 1 行的元素之和为 lower。第 i 列(从 0 开始编号)的元素之和为 cols…...

安全—01day
文章目录 1. 编码1.1 ASCLL编码1.2 URL编码1.3 Unicode编码1.4 HTML编码1.5 Base64编码 2. form表单2.1 php接收form表单2.2 python接收form表单 1. 编码 1.1 ASCLL编码 ASCII 是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是最通用的…...
【Git】Please commit your changes or stash them before you merge的解决方法
背景 我从远程库中clone了一个项目进行开发,修改了一部分代码后,远程库有更新,我想将远程更新拉取下来,并且保留自己的更改,使用git pull origin master命令,有报错: error: Your local chang…...

网卡收发包系统结构收发包流程,tcp/ip协议,socket套接字缓冲区,滑动窗口,mtu/mss
MTU和MSS的区别 MTU和MSS的区别 TCP 的 MTU & MSS MTU是在那一层?MSS在那一层? MTU是在数据链路层的载荷大小也就是传给网络层的大小,mss是在传输层的载荷大小也就是传给应用层的大小 mss是根据mtu得到的 1、MTU: Maximu…...
VUE之axios使用,跨域问题,拦截器添加Token
参考资料: 参考视频 视频资料及个人demo Axios中文文档 VUE之基本部署及VScode常用插件 VUE之基本组成和使用 VUE之Bootstrap和Element-UI的使用 准备工作: 关于SpringBoot和SpringCloud的搭建,以及mybatis-plus的整合见本人之前的CSDN博客,下面编写get请求和post请求…...
阿里云函数计算签名认证(iOS实现细节备注)
1、使用第三方库 AFNetworking进行网络请求。 2、阿里云函数计算签名认证文档 3、文档中添加 CanonicalizedFCHeaders 可以不用添加,CanonicalizedResource如何没有设置Path,在末尾加入“/”就可以了。 4、主要还是 hmac-sha256 签名认证,在实…...

成都爱尔蔡裕:泡在“糖”里的脆弱血管,暴露在眼睛深处
糖尿病是一组由多病因引起的以慢性高血糖为特征的终身性代谢性疾病。长期血糖增高,大血管、微血管受损并危及心、脑、肾、周围神经、眼睛、足等。医生临床数据显示,糖尿病发病后10年左右,将有30%~40%的患者至少会发生一种并发症&a…...
神经网络小记-过拟合与欠拟合
过拟合 过拟合(Overfitting)是机器学习和深度学习中常见的问题,指模型在训练数据上表现得非常好,但在新数据上表现较差,即模型过度拟合了训练数据的特征,导致泛化能力不足。 解决过拟合的方式包括以下几种…...

铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...

STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...

【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...

多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...

九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...