我认为分享的目的不是炫技。
- 自我学习的总结;
- 降低他人的学习成本;
- 别人对自己学习结果的审核;
我认为分享的目的不是炫技。
一、临界区 (Critical section) 临界区是并发编程中的一个术语,指的是一段访问共享资源的代码,比如对全局变量var sharedVariable int64进行递增操作,而递增操作需要包括读取变量当前值,递增该值,将该值写回内存变量中,这三个步骤就可以构成一个临界区。 在并发编程中,对共享资源的并发访问可能会导致意外或错误的行为,因此需要对程序中访问共享资源的部分进行保护,以避免并发访问。临界区包含对共享资源,如数据结构、外围设备或网络连接等进行访问。 如下图,互斥锁(Mutex Lock)提供了对临界区的互斥访问,通过使用锁的技术封锁一个临界区,而其他线程必须等待轮到自己进入该部分。这可以防止两个或更多的线程共享同一内存空间,并想访问一个共同的资源时发生冲突。 二、互斥锁 互斥锁是一种常用的控制共享资源访问的方法,它能够保证同时只有一个goroutine可以访问共享资源。 Go语言中使用sync包的Mutex类型来实现互斥锁。 使用互斥锁能够保证同一时间有且只有一个goroutine进入临界区,其他的goroutine则在等待锁;当互斥锁释放后,等待的goroutine才可以获取锁进入临界区,多个goroutine同时等待一个锁时,唤醒的策略是随机的。 package main import ( "fmt" "sync" ) var num int64 var wg sync.WaitGroup var mux sync.Mutex func main() { wg.Add(2) go add() go add() wg.Wait() fmt.Println(num) // output: 10000 } func add() { for idx := 0; idx < 5000; idx++ { mux.Lock() num++ // 非原子操作,因为有三个阶段在汇编中,分别内存到寄存器,寄存器自增,写回内存。 mux.Unlock() } wg.Done() } 三、读写互斥锁 互斥锁是完全互斥的,但是有很多实际的场景下是读多写少的,当我们并发的去读取一个资源不涉及资源修改的时候是没有必要加锁的,这种场景下使用读写锁是更好的一种选择。读写锁在Go语言中使用sync包中的RWMutex类型。 读写锁分为两种:读锁和写锁。 当一个goroutine获取读锁之后,其他的goroutine如果是获取读锁会继续获得锁,如果是获取写锁就会等待;当一个goroutine获取写锁之后,其他的goroutine无论是获取读锁还是写锁都会等待。 需要注意的是读写锁非常适合读多写少的场景,如果读和写的操作差别不大,读写锁的优势就发挥不出来。 package main import ( "fmt" "sync" "time" ) var ( num int64 wg sync....
报错内容 $ clear 'tmux-256color': unknown terminal type. 排查结果 在tmux中登陆ssh,使用错误的终端仿真器类型导致的。 TERM是一个环境变量,它定义了终端类型或终端仿真器的类型。它告诉操作系统和应用程序如何与终端交互。 在Linux系统中,TERM变量通常由终端仿真器或终端程序设置,用于指定当前使用的终端类型。不同的终端类型有不同的功能和特性,如颜色支持、光标移动、特殊键盘映射等。 TERM的值通常是一个标识符,如xterm、gnome、konsole等,表示所使用的终端类型。这些标识符在系统中会有对应的配置文件,用于定义该终端类型的功能和特性。当使用tmux登陆ssh后,TERM被修改为tmux-256color,最终导致显示问题。 在Linux中,可以使用echo命令来查看当前的TERM变量的值: echo $TERM 通过设置TERM变量,可以让应用程序根据当前终端类型来做出相应的调整,以保证在不同终端下的正确显示和交互。 解决方案 alias ssh='TERM=xterm-256color \ssh' alias ssh='TERM=xterm-256color /usr/bin/ssh' 参考 https://github.com/alacritty/alacritty/issues/1208 https://blog.slowb.ro/fix-terminal-capability-cm-required/
一、理解 MySQL 内存使用 我们知道所有服务在运行时都会有内存开销,但MySQL等在内的服务会预先分配内存。即使是MySQL服务相对空闲的时候,MySQL的内存使用率依然是非常高的。这可能会让人觉得服务异常,但实际上它是正常情况。 InnoDB 缓冲池 可以说MySQL最重要的组件就是InnoDB缓冲池。每次对表进行读写时,都会将records或indexes所在的页面加载到缓冲池中。 这意味着,如果你读写最多的数据的页面都在缓冲池中,那么性能就会比从磁盘读取页面更好。如果缓冲池已经满了,没有空闲页面时,就必须淘汰驱逐旧的页面。 在MySQL中,一个表被分为多个页面(page),每个页面的默认大小默认为16KB。MySQL将表分为多个页面的主要目的是为了提高查询效率。因为如果把整个表都加载到缓冲池中,那么缓冲池的大小可能非常大,这将会导致内存不足的问题。因此将表分为多个页面可以更好的利用内存。 MySQL 8.0 Reference The buffer pool is an area in main memory where InnoDB caches table and index data as it is accessed. The buffer pool permits frequently used data to be accessed directly from memory, which speeds up processing. 缓冲池是InnoDB在内存中缓冲table和index数据的区域。缓冲池允许直接从内存访问常用数据,从而加快了处理速度。 How MySQL Uses Memory InnoDB allocates memory for the entire buffer pool at server startup, using malloc() operations. The innodb_buffer_pool_size system variable defines the buffer pool size....
一、什么是 DAP 技术 DAP全称Debug Adapter Protocol,和LSP一样,是由微软推出,并在VSCode中使用的标准化协议。它允许编辑器和集成开发环境(工具)将以前必须在每个编辑器中实现的大部分逻辑外包给一个称为调试适配器的中介。 调试适配器可以在多个工具中重复使用,从而使编辑器开发人员的开发时间更快,普通开发人员的用户体验更好,因为他们可以在多个编辑器中获得类似的顶级代码帮助。 在没有 DAP 技术以前 在没有DAP之前,每次开发一个新的编辑器,并希望在其中支持代码调试时,都必须处理所有功能的实现细节,例如 breakpoints Watch expressions step in, step out etc … 除此以外,你还得负责所有用户界面的实现,所有这些对于工具的开发人员来说当然都是非常大的工作量,有些工具根本负担不起。 有了 DAP 技术以后 现在,有了 DAP 编辑器,开发人员只需集中精力: 集成 DAP 客户端,处理与调试适配器的所有通信 专注于用户界面开发,以发送或显示来自调试适配器的数据 二、安装 DAP 客户端 当前Neovim不像LSP一样内置了DAP客户端,所以我们需要手动的去安装DAP客户端。这里我们使用mfussenegger/nvim-dap作为DAP客户端。 除了安装DAP客户端以外,还需要安装debugger调试器,这里我们用go举例安装dlv调试器。 DAP 客户端如何与 Debugger 连接 通过一些外部插件,例如在上面的例子中,可以使用nvim-dap-go插件,该插件通过使用 nvim-dap 插件提供的应用程序接口,告诉 Neovim 应如何启动调试器(在我们的例子中为 delve)。 首先会检查delve是否安装,如果没有安装则会报错。 其次会进行启动delve,并将其作为一个服务。 然后nvim-dap-go告诉nvim-dap,它可以通过附加到上一步启动的服务器来连接 delve。
Linux 进程间通信类型 在Linux中,进程间通信(Inter-Process Communication, IPC)机制允许多个进程之间传递信息,常见的通信的方式有管道、信号、共享内存、消息队列、信号量和套接字。实现不同进程之间的数据共享和协作,更好完成各自任务。 1. Pipes - 管道 管道是最基本的IPC机制,通过在两个或多个进程之间,创建单向或双向通道,允许它们相互通信。比如ls | grep abc命令中,就将ls这个进程的输出,作为grep进程的输入进行传递。 2. Signal - 信号 信号是向进程或同一进程中的特定线程发送的异步通知,目的是向其通报某一事件。信号的常见用途是中断、挂起、终止或杀死进程。通过kill -l可以列出所有信号类型,因为在Linux中,大部分信号都是将进程给杀死,所以统一称为kill。 在开发过程中,可以让程序捕捉并处理kill进程发送来的信号。 但是有个约定俗成的约定,kill -9信号,也就是sigkill信号是不可以被捕捉并处理的,即程序接收到kill -9信号,则必须得关闭。 3. Shared Memory - 共享内存 共享内存允许两个或多个进程共享一个内存区域,这段共享内存由一个进程创建,但多个进程都可以访问。因为数据不需要在进程之间复制,所以是较快的IPC通信方式 4. Message Queues - 消息队列 消息队列是一个先进先出(FIFO)的数据结构,允许一个或多个进程写入消息,并且一个或多个进程读取消息。消息队列提供了异步通信能力,进程可以不同步的发送或接收消息,这意味着发送者不需要等待接收者处理消息,反之亦然。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。 5. Semaphores - 信号量 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。 6. Socket - 套接字 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。例如访问远端的MySQL服务器。需要注意,如果服务在本地,则套接字可以使用的unix套接字,而非TCP等套接字。
需求描述 当使用Go语言去构建对象时,通常会调用相应的New方法,去进行初始化的操作。如下代码所示。 但若以后增加新的字段配置,则会破坏函数的兼容性,例如增加超时时间等。 func NewServer(addr string, port int) (server *http.Server, err error) { server = &http.Server{ Addr: fmt.Sprintf("%s:%d", addr, port), } return } Functional Options Pattern Functional Options Pattern简称FOP,是一种软件设计模式。它将函数的核心逻辑的实现,与它的可选参数(功能选项)的实现分开。 这样就可以轻松地修改和定制函数的行为,而不需要担心破坏函数的兼容性问题。 type options struct { port *int // 字段参数,尽量使用指针的方式,去判定是否有设定该字段。 handler *http.Handler readTimeout *time.Duration writeTimeout *time.Duration } type Option func(options *options) error func WithPort(port int) Option { return func(options *options) error { if port < 0 { return errors.New("http port should be positive") } options....
我们知道Redis是基于内存的键值对数据库,所以查询速度自然要比MySQL更快。但是为什么Redis可以支持数以万计的并发请求,并且单台Redis的QPS是MySQL的十倍多。 换句话说,每次客户端对服务端进行网络请求时,都会在进程内生成相应的FD文件,Redis是如何高效的监听并处理这些连接的。 这里主要是利用了IO multiplexing & Single-threaded read/write。 一、单线程模型 Single-threaded read/write 使用多线程通常会带来更大的吞吐率和更短的响应时间,然而,使用多线程并不一定比单线程程序快。CPU在一个时间片中只执行一个线程,当内核线程切换时,它需要保存线程A的执行环境,然后加载线程B的执行环境。线程执行上下文切换的开销并不低,如果没有必要,应该尽量减少。而且,使用多线程往往会使设计更加复杂,要求我们在访问共享数据时更加小心–对数据加锁对Redis来说不是一个划算的选择。 绝大多数的Redis请求都是纯粹的内存操作,这个过程非常快,CPU并不是性能瓶颈,即使是单线程实例,单个实例也可以每秒处理数万个数据请求。对于更高的并发性要求,Redis还支持集群部署,即在一台或多台服务器上启动多个实例,以分担访问压力。 所以,从产品定位上,Redis不是一个CPU密集型应用,所以单线程足以满足计算需求。 二、多路复用模型 I/O Multiplexing 在类Unix系统中,内核使用File Descriptor,也即FD去标识一个文件或者一个I/O资源(网络套接字),这些FD被特定的进程所访问。使用不同的I/O模型去访问FD,所访问的模式也不同,常用包含五种I/O模型: 阻塞式I/O 非阻塞式I/O I/O多路复用 ⭐️ 信号I/O 异步I/O Redis使用的是I/O多路复用。对于I/O多路复用,Linux提供了select/poll/epoll这三个系统调用,进程可以使用这些进程调用,从内核中监听多个文件描述符。 1. select/poll 通过使用线性结构存储进程监听的FD集合。即把文件描述符集合拷贝到内核中,让内核检查是否有事件产生,检查的方式很粗暴,通过遍历的方式去遍历文件描述符集合,当检查到有事件产生时,标记可读可写,接下来再把整个文件描述符集合拷贝到用户态中,然后用户态还需要通过遍历的方式找到可读可写的FD。 select使用BitsMap(最大监听1024个FD)、poll使用链表(突破了select的限制)。 2. epoll 通过使用红黑树监听待检测的FD。红黑树是个高效的数据结构,增删改一般时间复杂度是O(logn)。通过对这棵黑红树的管理,可以扩展到更大数量的连接,且其性能不会随着被监视的fd数量增加而显著下降,并且不需要像select/poll在每次操作时都传入整个FD集合,减少了内核和用户空间大量的数据拷贝和内存分配。 在Redis和Golang中,底层都是通过epoll系统调用,实现网络I/O多路复用功能。它能够高效处理成千上万个并发网络连接至关重要,尤其是在构建需要高吞吐量和低延迟响应的服务应用时。这点与MySQL不同,MySQL是一个连接分配一个线程的模式。 参考 https://blog.bytebytego.com/p/why-is-redis-so-fast https://www.sobyte.net/post/2022-08/redis-single-thread/
因工作和学习所需,最近购买了一台友善之臂的R4S软路由,以实现家庭网络出口代理等功能。但当我在软路由中正确配置并启动代理服务后,仍然无法正常访问外网。 一、排查步骤 1. 检查出口IP地址 通过使用curl -s 'https://ipapi.co/json'检查出口外网的IP地址是否为代理的IP地址。 分析终端输出的结果,代理配置是没有问题的,出口IP确实为代理IP。仍然无法访问外网,则很可能是本机DNS缓存导致的,需要刷新本地DNS缓存。🚫 2. 刷新DNS缓存 各个操作系统,甚至是特定操作系统底下的不同版本,刷新本地DNS缓存的命令都不相同。我这里使用的是CleanMyMac X应用执行刷新本地DNS缓存。 操作完成以后,打开Safari输入外网地址后可以成功进行访问。✅ 二、什么是DNS缓存 使用DNS目的是将你的域名映射到相关的IP地址,例如将baidu.com映射39.156.66.10上。而DNS缓存可以看作是你Mac上的一个本地存储区,它会在暂时存储并跟踪你计算机的活动,比如最近的网站访问等。当你第二次访问相同网站时,查询过程更加高效且时间更短,节省大量的时间。 但是本地缓存信息可能会随着时间的推移变得过时,当网站发生DNS更新时,你的Mac仍在使用旧的、不准确的信息来加载请求的页面。所以,刷新DNS缓存可以确保缓存信息是最新的。