首页 > 单线程多路复用和多线程加锁的区别

单线程多路复用和多线程加锁的区别

看介绍说redis是单线程的,通过epool实现多路复用。由于是单线程所以对资源的访问是串行的,不会产生资源竞争。然后突然有个疑惑,既然对资源的访问是串行的,也就是说如果我某个请求要set,而前面排着K个同样要set的操作,我还是得等它们set完成我再能set。然后有几点疑问,希望大牛不吝赐教:


线程切换很慢,多线程锁很慢,lock很慢,用户态核心态切换很慢,要是不在乎这些很慢,就没啥了。


来回答前同事的一部分问题

首先你要知道redis除了持久化,几乎所有操作都是在操作内存,比如像简单的set get操作都非常快,具体多快我觉得你可以自己来做一个benchmark,并不难
如果你关注新闻的话可以知道双11阿里的交易巅峰值是14w笔/s
这基本已经是国内it界最高的并发了(那种几亿同时在线的不算),你再去想想操作内存的时间,不考虑事务,我只把14w条数据记下来看起来并不是什么难事对不对

一台写不过来我十台总行了吧,所以除非你的set的value本身特别大,不用担心在操作时的等待时间,就算有1w个请求过来,你还是在操作内存,严格意义上说,redis本身应对的业务场景并不是一个高并发的场景,你看一下redis本身默认的连接数设置应该也就懂了

应对这种场景,你用多线程+锁也没有什么问题(当然性能可能会差一点点),之前tim大神做过一个memcached和redis的性能对比,虽然年代久远,不过也可以说明一些问题,要知道memcached就是多线程+锁的模型,两者看起来差别也没有太过夸张。虽然结果是redis好一些。

看到这里你是不是觉得单线程+io复用赢了?这可真不一定。。只是在这种场景下赢了而已,本身单线程io复用和多线程+锁其实只是两种编程模型,两种模型也都是为了解决问题,哪种优要看具体的业务场景,这里还是要说了,不服跑分啊

go的goroutine本质是green threads,runtime来调度的用户态线程,其实这种概念在其他语言里也有,只是其他语言都是以第三方库来做这件事情,go把它集成在了语言内,并且不用你自己去管理调度的事情,go语言里的实现只是让你可以更方便地写而已,所以这东西并不是银弹,不用太过迷信,go所带来的更重要的是开发效率的提升,并没有解决什么具体的问题。

关于epoll,go语言的net库底层也是用epoll来做io复用的(仅指linux平台),epoll这个东西只是linux下的一种io复用的实现,在其他的发行版里还有其他变种,而程序员们其实不太想关心你这些事情,他们希望在linux下写的程序去freebsd还能跑,所以libevent棒棒哒,当然你写go的话,这些事情不用操心。


对于key/value并不长的情况,比如二三十个字节,redis在2.4ghz的机器跑个几万每秒的set没什么压力,所以你不用担心说会等的情况,如果这个都还不够快,你该考虑加进程加机器。关于单线程多线程有什么区别的问题,这个只是编程模型的不同,简单一点的场景,如果你的应用每台机器都自己独立部署,它的请求也都是来自本机,你用单线程多线程都OK,如果你想利用多核,那显然单线程是不够的,你需要跑多个实例,然后在它前面有一个服务来做请求分配,如果你是多线程,那么可以由一个线程来干这个活,就只需要一个服务就够了。关于性能,如果你都是干一样的活,并且你的线程数量并不太多,那性能上应该几无差异或者差异很小,这里影响的还是吞吐。假设你的代码除了访问这块的模型差异外,其他地方都一样,那就取决于你这个竞争访问的粒度了,就是这个锁锁住的代码执行是否是耗时长的,锁并不慢,慢的是竞争,除非你每秒要做几十几百万次加解锁,理论上,如果你的粒度比较小,多线程的吞吐一定是大于单线程的,除非你的粒度很大,大到每一个请求都串行处理了,这样就已经失去了多线程的意义。吞吐上去了,当然所使用的 cpu 自然也上去了,或者说使用了更多的 cpu 资源,吞吐上去了。

【热门文章】
【热门文章】