为保证上述整个操作的原子性,防止在GET之后,DELETE之前的期间Redis恰巧把锁给自动释放了,一般把上述的过程写到一个Lua的脚本中提交给Redis执行,因为Redis执行Lua脚本中的命令是原子性质的。
Win32 API不同于POSIX,数量很大,且不一定都会陷入系统内核进行执行
匿名管道顾名思义,它没有名字标识,匿名管道是特殊文件只存在于内存,没有存在于文件系统中,shell 命令中的「|」竖线就是匿名管道,通信的数据是无格式的流并且大小受限,通信的方式是单向的,数据只能在一个方向上流动,如果要双向通信,需要创建两个管道,再来匿名管道是只能用于存在父子关系的进程间通信,匿名管道的生命周期随着进程创建而建立,随着进程终止而消失。命名管道突破了匿名管道只能在亲缘关系进程间的通信限制,因为使用命名管道的前提,需要在文件系统创建一个类型为 p 的设备文件,那么毫无关系的进程就可以通过这个设备文件进行通信。另外,不管是匿名管道还是命名管道,进程写入的数据都是缓存在内核中,另一个进程读取数据时候自然也是从内核中获取,同时通信数据都遵循先进先出原则,不支持 lseek 之类的文件定位操作。消息队列克服了管道通信的数据是无格式的字节流的问题,消息队列实际上是保存在内核的「消息链表」,消息队列的消息体是可以用户自定义的数据类型,发送数据时,会被分成一个一个独立的消息体,当然接收数据时,也要与发送方发送的消息体的数据类型保持一致,这样才能保证读取的数据是正确的。消息队列通信的速度不是最及时的,毕竟每次数据的写入和读取都需要经过用户态与内核态之间的拷贝过程。共享内存可以解决消息队列通信中用户态与内核态之间数据拷贝过程带来的开销,它直接分配一个共享空间,每个进程都可以直接访问,就像访问进程自己的空间一样快捷方便,不需要陷入内核态或者系统调用,大大提高了通信的速度,享有最快的进程间通信方式之名。但是便捷高效的共享内存通信,带来新的问题,多进程竞争同个共享资源会造成数据的错乱。那么,就需要信号量来保护共享资源,以确保任何时刻只能有一个进程访问共享资源,这种方式就是互斥访问。信号量不仅可以实现访问的互斥性,还可以实现进程间的同步,信号量其实是一个计数器,表示的是资源个数,其值可以通过两个原子操作来控制,分别是 P 操作和 V 操作。与信号量名字很相似的叫信号,它俩名字虽然相似,但功能一点儿都不一样。信号是进程间通信机制中唯一的异步通信机制,信号可以在应用进程和内核之间直接交互,内核也可以利用信号来通知用户空间的进程发生了哪些系统事件,信号事件的来源主要有硬件来源(如键盘 Cltr+C )和软件来源(如 kill 命令),一旦有信号发生,进程有三种方式响应信号 1. 执行默认操作、2. 捕捉信号、3. 忽略信号。有两个信号是应用进程无法捕捉和忽略的,即 SIGKILL 和 SEGSTOP,这是为了方便我们能在任何时候结束或停止某个进程。前面说到的通信机制,都是工作于同一台主机,如果要与不同主机的进程间通信,那么就需要 Socket 通信了。Socket 实际上不仅用于不同的主机进程间通信,还可以用于本地主机进程间通信,可根据创建 Socket 的类型不同,分为三种常见的通信方式,一个是基于 TCP 协议的通信方式,一个是基于 UDP 协议的通信方式,一个是本地进程间通信方式。以上,就是进程间通信的主要机制了。你可能会问了,那线程通信间的方式呢?同个进程下的线程之间都是共享进程的资源,只要是共享变量都可以做到线程间通信,比如全局变量,所以对于线程间关注的不是通信方式,而是关注多线程竞争共享资源的问题,信号量也同样可以在线程间实现互斥与同步:互斥的方式,可保证任意时刻只有一个线程访问共享资源;同步的方式,可保证线程 A 应在线程 B 之前执行;
Ctrl+C 产生 SIGINT 信号,表示终止该进程; Ctrl+Z 产生 SIGTSTP 信号,表示停止该进程,但还未结束; kill -9 1050 ,表示给 PID 为 1050 的进程发送 SIGKILL 信号,用来立即结束该进程; 所以,信号事件的来源主要有硬件来源(如键盘 Cltr+C )和软件来源(如 kill 命令)。
共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中。这样这个进程写入的东西,另外一个进程马上就能看到了,都不需要拷贝来拷贝去,传来传去,大大提高了进程间通信的速度。
匿名管道(内存中,fork复制了管道fd,父子进程通信) 命名管道(文件中,不同进程间可以通信) fd[2],一个用来写 一个用来读
MVCC 实现的,即 Mutil-Version Concurrency Control
RedLock是Redis之父Salvatore Sanfilippo提出来的基于多个Redis实例的分布式锁的实现方案。其核心思想就在于使用多个Redis冗余实例来避免单Redis实例的不可靠性。比如我们采用5个Redis实例,我们可以把5个Redis全部部署到同一台机器上,也可以把5个Redis部署在5个不同的机器上。一般为了实现更好的读写性能以及抗风险能力,我们选择部署5个Redis在5个机器上。
python dir(object)可以查看所有的属性和方法

总结 gin框架路由使用前缀树,路由注册的过程是构造前缀树的过程,路由匹配的过程就是查找前缀树的过程。gin框架的中间件函数和处理函数是以切片形式的调用链条存在的,我们可以顺序调用也可以借助c.Next()方法实现嵌套调用。借助c.Set()和c.Get()方法我们能够在不同的中间件函数中传递数据。 c *gin.Context
gin框架源码算是比较简单易懂的,这恰恰就是他的优点,golang语言本身就比较成熟,框架只不过一个方便你做项目脚手架,你完全可以按照你的需求来定制你自己专属的gin框架,包括日志,缓存,队列等等 核心是路由存储树,学好算法,数据结构才是关键
浮点数小数点后的数必须要以5结尾才能完全精确表示,因为2^{-1},2^{-2}都是以5结尾的
看了一篇C++模板特化以及模板偏特化的文章,讲得挺好的,https://www.jianshu.com/p/4be97bf7a3b9,最重要的就是偏特化保留了templatize,而特化不保留templatize



只有指针和引用才能使用多态


元数据(meta data)——“data about data” 关于数据的数据,一般是结构化数据(如存储在数据库里的数据,规定了字段的长度、类型等)。 元数据是指从信息资源中抽取出来的用于说明其特征、内容的结构化的数据(如题名,版本、出版数据、相关说明,包括检索点等),用于组织、描述、检索、保存、管理信息和知识资源。 https://www.zhihu.com/question/20679872/answer/65565699
简单补充一下pipeline,HTTP1.1中允许多个请求复用连接,同时可以一口气将请求全部发出去,不用一个返回后再发送第二个,提升并发性。而服务端需要将请求的结果,按照pipeline中发送的顺序进行顺序返回,如果靠前的请求阻塞了,那么靠后请求返回就会被动等待。
在系列(一)中,我们从全局鸟瞰了RPC,其有三大特点: 具有需要约定调用语法 需要约定内容编码方式 需要网络传输 所有RPC框架都是在围绕这几个点不断优化,以更优的方案,达到更低的成本,更快的速度。要想达到这个目的,内容编码方式就是一个非常重要的点,RPC调用的request和response内容在调用过程中有着不小的消耗: 内容的序列化、反序列化,如果效率更高,则对CPU消耗会更小 内容会在网络中传输,协议栈拷贝成本、带宽成本、GC等。体积越小,效率越高
到此,我们可以从分布式系统的角度发现: RPC是分布式系统通信的一种工具。
所谓赋能,用人话来讲,就是能低成本大规模使用,一般就几个需求: 有良好的适应能力。对于部分核心能力可以通过插件实现定制化能力,适应不同的环境。 提供关键性问题解决方案。例如高效率的并发模型、加密、压缩等等。 有足够的扩展能力。能通过配置、注入等方式灵活实现扩展功能或开启部分功能。 足够简单。屏蔽底层细节,能无脑上手使用,不需要懂http2、protobuf、IO模型等等是什么
简单理解为以下过程:
注册一个名称系统的实例到gRPC (一般启动时注册, 可以注册任意数量)
A 通过gRPC调用B时,gRPC会解析出B的scheme,从注册的名称系统获得可用的服务地址列表,一般是一批 IP:port (IP:port如何得到gRPC不关心,使用方根据自身情况实现即可)
gRPC针对IP:port建立网络连接
gRPC将请求发出去,接收回复
Reactor模型不仅仅用来处理IO事件,同时其实更多的是作为一个高并发的处理模型,如一个好的RPC框架,面对高并发的远程调用时需要使用Reactor这样的模型来进行分发处理
线程池的工作原理 (1)如果当前运行的线程少于corePoolSize(核心线程数),则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁)。 (2)如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue(阻塞队列/任务队列)。 (3)如果无法将任务加入BlockingQueue(队列已满),则在非corePool中创建新的线程来处理任务(注意,执行这一步骤需要获取全局锁)。 (4)如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并执行线程饱和策略,如:RejectedExecutionHandler.rejectedExecution()方法。
注意: (1)线程池初始化时,是空的。如果线程池中的线程数少于核心线程数,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务。 (2)如果阻塞队列已满,且当前线程数<maximumPoolSize,则新建线程执行该任务。而不是新建线程,从阻塞队列里take任务来执行,所以这里并不是先来先执行的。 (3)阻塞队列用于存放任务,无法执行任务,起到一个缓冲的作用。阻塞队列的长度与最大线程数目无关。
————————————————
左值引用, 使用 T&, 只能绑定左值
右值引用, 使用 T&&, 只能绑定右值
常量左值, 使用 const T&, 既可以绑定左值又可以绑定右值
已命名的右值引用,编译器会认为是个左值
编译器有返回值优化,但不要过于依赖
需要注意一下几点: str6 = std::move(str2),虽然将str2的资源给了str6,但是str2并没有立刻析构,只有在str2离开了自己的作用域的时候才会析构,所以,如果继续使用str2的m_data变量,可能会发生意想不到的错误。 如果我们没有提供移动构造函数,只提供了拷贝构造函数,std::move()会失效但是不会发生错误,因为编译器找不到移动构造函数就去寻找拷贝构造函数,也这是拷贝构造函数的参数是const T&常量左值引用的原因! c++11中的所有容器都实现了move语义,move只是转移了资源的控制权,本质上是将左值强制转化为右值使用,以用于移动拷贝或赋值,避免对含有资源的对象发生无谓的拷贝。move对于拥有如内存、文件句柄等资源的成员的对象有效,如果是一些基本类型,如int和char[10]数组等,如果使用move,仍会发生拷贝(因为没有对应的移动构造函数),所以说move对含有资源的对象说更有意义。


fork&vfork&clone 创建进程&创建不copy资源的进程&创建线程 https://blog.csdn.net/gatieme/article/details/51417488 写时复制 有人认为这样大批量的复制会导致执行效率过低。其实在复制过程中,linux采用了写时复制的策略。
子进程复制了父进程的task_struct,系统堆栈空间和页面表,这意味着上面的程序,我们没有执行count++前,其实子进程和父进程的count指向的是同一块内存。而当子进程改变了变量时候(即对变量进行了写操作),会通过copy_on_write的手段为所涉及的页面建立一个新的副本。
所以当我们执行++count后,这时候子进程才新建了一个页面复制原来页面的内容,基本资源的复制是必须的,而且是高效的。整体看上去就像是父进程的独立存储空间也复制了一遍。
open文件时的文件结构体是存放在内核空间的,其实用的也是mmap,所以多个进程可以同时访问
分支预测 会让cpu在处理排序数组时更快 https://www.jianshu.com/p/19f98ca9a99e

ET模式下每次write或read需要循环write或read直到返回EAGAIN错误。以读操作为例,这是因为ET模式只在socket描述符状态发生变化时才触发事件,如果不一次把socket内核缓冲区的数据读完,会导致socket内核缓冲区中即使还有一部分数据,该socket的可读事件也不会被触发
根据上面的讨论,若ET模式下使用阻塞IO,则程序一定会阻塞在最后一次write或read操作,因此说ET模式下一定要使用非阻塞IO
比如说read() 如果此时buff里面有5bytes的数据,而我们要read()10bytes的数据 对于block:那么只有等到buff里面有10bytes的数据是read()才返回,不管多长时间都要等待 对于nonblock:马上返回5bytes的字节
P&V,sem_wait(sem_t* sem),sem_post(sem_t* sem)


