Linux | c&cpp | Email | github | QQ群:425043908 关注本站

itarticle.cc

您现在的位置是:网站首页 -> 代码相关 文章内容

多线程编程使用无锁结构会不会比有锁结构更快?-itarticl.cc-IT技术类文章记录&分享

发布时间: 8年前代码相关 129人已围观返回

一部分朋友觉得用锁会影响性能,其实锁指令本身很简单,影响性能的是锁争用(Lock Contention),什么叫锁争用,就是你我都想进入临界区,但只能有一个线程能进去,这样就影响了并发度。可以去看看glibc中pthread_mutex_lock的源码实现,在没有contention的时候,就是一条CAS指令,内核都没有陷入;在contention发生的时候,就选择陷入内核然后睡觉,等待某个线程unlock后唤醒(详见Futex)。

“只有一个线程在临界区”这件事对lockfree也是成立的,只不过所有线程都可以进临界区,最后只有一个线程可以make progress,其它线程再做一遍。

所以contention在有锁和无锁编程中都是存在的,那为什么无锁有些时候会比有锁更快?他们的不同体现在拿不到锁的态度:有锁的情况就是睡觉,无锁的情况就不断spin。睡觉这个动作会陷入内核,发生context switch,这个是有开销的,但是这个开销能有多大呢,当你的临界区很小的时候,这个开销的比重就非常大。这也是为什么临界区很小的时候,换成lockfree性能通常会提高很多的原因。

再来看lockfree的spin,一般都遵循一个固定的格式:先把一个不变的值X存到某个局部变量A里,然后做一些计算,计算/生成一个新的对象,然后做一个CAS操作,判断A和X还是不是相等的,如果是,那么这次CAS就算成功了,否则再来一遍。如果上面这个loop里面“计算/生成一个新的对象”非常耗时并且contention很严重,那么lockfree性能有时会比mutex差。另外lockfree不断地spin引起的CPU同步cacheline的开销也比mutex版本的大。

lockfree的意义不在于绝对的高性能,它比mutex的优点是使用lockfree可以避免死锁/活锁,优先级翻转等问题。但是因为ABA problem、memory order等问题,使得lockfree比mutex难实现得多。

除非性能瓶颈已经确定,否则还是乖乖用mutex+condvar,等到以后出bug了就知道mutex的好了。如果一定要换lockfree,请一定要先profile,profile,profile!请确保时间花在刀刃上。


自旋锁的本质还是应用层的锁,当一个线程持有锁后,被调度出去了,其它线程还是无法继续,而lockfree不是这样的,它可以保证至少一个线程make progress.被调度出去指操作系统的调度,此时还线程占着锁,其它线程自然无法继续,而lockfree没有锁当然没这个问题


在目前的mutex实现前提下, 用lock-free一般不会更快. 它们都只能同时让一个线程做有用的事, 在性能上不会有质的区别. 由于lock-free的代码会明显复杂, 性能还未必更高. 所以除非场景特殊, 用lock-free并不是个好主意.

更好的方法是减少数据共享, 比如你这里分多个桶加锁就行了, 性能会比单纯用一套lock-free的数据结构更好. 在追求极限性能的场景中,一般考虑的是wait-free的算法和容器, 这能让所有线程同时做有用的事, 在关键代码上相比加锁和lock-free才有较大的区别.


个人认为更多的应该把精力放在架构上,无锁和有锁,你这样测试估计不太可能测出什么区别来,也不是流量越大,某种做法就更优的,你要知道无锁和有锁的实际差别到底在哪里,不是那几个指令的开销,而是精确的掌控整个cpu调度体系的问题。

我们的目的应该是精确的控制cpu调度,在有限资源的情况下,如何更好的服务更多的客户端(有锁无锁只是达成这个目标的手段而已)。

单台服务器,cpu占用过高过低都不优;内核频繁切换是不优的;频繁的竞争是不优的;无意义的内存占用是不优的;客户端得不到必要的反馈是不优的;不方便后期维护是不优的。

举些例子来说,如果逻辑代码很复杂,可能会严重的阻塞网络层,这时,无锁没有任何好处,处理不好反而会让内存爆掉,有时,把阻塞反馈给客户端是非常有必要的。

如果第三方IO导致阻塞很严重(等待其它服务器的返回),cpu闲置率过高,这时不做特殊优化,估计也没什么差别,看你优化的方向了,如果是需要合并一些操作,个人觉得无锁的会容易处理一些。

如果各方面都很平衡,但逻辑上需要完全的序列化,我也会倾向于无锁。

所以,有锁无锁取决于你整体的考量,总会有阻塞点或者瓶颈,针对性的解决最关键的点,在效率优先的情况下考虑效率,在稳定性优先的情况下优先稳定性。


我以前也做过接收FPGA高速发包的上位机程序。我那时候场景是这样:下位机FPGA,千兆直连网络,上位机Windows。

一开始用Qt的network开发,丢包丢到惨不忍睹,注意Qt在Windows下的底层就是select。后来换ASIO,发现也没好多少。于是再下一个,最后换了WinPcap(题主是Linux,不适用),问题解决。

中间遇到过很多坑,但是总体来说,做到以下几点基本没问题:

1.单线程收,反正我没上多线程,也只开了一个进程就搞定了

2.上不上锁无所谓,不要发生锁争夺就可以

3.缓冲区一次准备好,也就是说要事先分配,不要动态new或者malloc。因为动态分配内存都是有开销的,再细微的开销,对于UDP这样性质和FPGA这样高速设备,数量上来了肯定就会丢。

4.在接收的时候不做任何数据处理,注意是不做任何的数据处理,只管把数据memcpy到准备好的缓冲区里,然后马上回去继续接收,等一轮接收结束再拖走整个缓冲区去处理。

最后做出来,千兆网络大约跑到600M的样子(很惭愧没跑满),UDP包在和大小不是1472这个最大值,因为FPGA那里存储逻辑的问题被改过,不过也是1000以上,具体不记得了。

至于丢包嘛,每轮发送30MB数据,也就是每轮大约3万个包。大约10轮里面会有1轮发生丢包,可能丢几个或者几十个,其他全能接到(0丢包)。


首先要看你的程序能并行的程度到底多高,诸如消息队列这种通常不是太饱就是空转,很难达到生产消费速度一致。

多线程很多时候不是为了提升效率,而是为了减少依赖。


发布时间: 8年前代码相关129人已围观返回回到顶端

很赞哦! (1)

文章评论

  • 请先说点什么
    热门评论
    128人参与,0条评论

站点信息

  • 建站时间:2016-04-01
  • 文章统计:728条
  • 文章评论:82条
  • QQ群二维码:扫描二维码,互相交流