地球人都知道,线程之间同步采用互斥锁的粒度太大,容易导致等待,以及上下文切换。再后来又出现读写锁,减少锁的几率。这一点,不得不说linux还是厉害。std::shared_mutex在c++17才出现,windows server 2008才提供。
而自旋锁,这种更低粒度的锁,在Linux内核中遍地都是,windows在驱动程序倒是有接口,而用户层到现在还没有影子。
我们知道,内核态和用户态的切换会损失不少性能。为了尽量减少锁定的次数,导致陷入内核态,Linux提供了一个接口名为FUTEX,全称为fast userspace mutex,看这个名字就知道他是干啥的。
不管怎么样,到了最后都无法避免线程切换。但能不能自己触发线程切换呢?别说,还真有这个函数,名字叫yield。windows和c++下也有类似的,底层指令都是pause。有了这个函数,和自旋锁结合起来,可以做很多事情。
虽然有很多方法减少线程之间的锁粒度,但线程之间的同步总是会影响性能。早期有Jemalloc和后来tcmalloc,两者关键原理都差不多。
1、用线程变量,使得内存分配尽可能在线程内部就能完成,不需要和其他线程竞争。
2、通过线程号,映射到一个数组某个固定索引元素。同样可以减少多个线程竞争的概率。
在我早期《jemalloc原理概览》微信文章中,曾专门做个介绍,这里不重复再讲。
以上两个方法,把锁粒度都去掉了,干脆就不需要竞争。在现代编程,线程变量越来越容易使用,对提高性能是个不错的解决方案。
现在很多人会提到协程、纤程之类的概念,我只能说,这些都是好主意,特别是I/O频率比较高的场景下,而且代码写得也容易理解。但对性能提高真没有用。