偶然发现的严重问题,所以提醒下各位用nodejs写web或者其他程序的人们:程序可能死锁或者数据不对,导致程序挂掉,具体是这样子的: nodejs的所有异步操作都依赖跨平台开源库libuv。 libuv里面为了实现线程间同步,使用了读写锁。 windowsNT6平台,系统自带SRWLock,所以此时libuv会使用系统提供的读写锁接口,这个没问题。 问题出在NT5平台: NT5平台没有内建的读写锁实现,所以libuv自己实现了一套读写锁,大概逻辑是这样的://thread.c //这里是加读锁,实际也就是将写锁阻塞,使得写操作的线程等待,同时将读计数+1 //具体来说: //如果计数+1后的值是1,说明是第一个读线程,所以实际上之前没做阻塞写线程的事情,这里真正做一次阻塞写线程的事情,也就是 // if (++rwlock->fallback_.num_readers_ == 1) // uv_mutex_lock(&rwlock->fallback_.write_mutex_); // 这一句的任务inline static void uv__rwlock_fallback_rdlock(uv_rwlock_t* rwlock) { uv_mutex_lock(&rwlock->fallback_.read_mutex_); if (++rwlock->fallback_.num_readers_ == 1) uv_mutex_lock(&rwlock->fallback_.write_mutex_); uv_mutex_unlock(&rwlock->fallback_.read_mutex_); } //这里是释放读锁,实际也就是将写锁解除阻塞,使得阻塞的写操作线程被唤醒,同时将读计数--1 //如果计数-1后的值是0,说明没有线程在读了,可以让阻塞的写线程唤醒了 inline static void uv__rwlock_fallback_rdunlock(uv_rwlock_t* rwlock) { uv_mutex_lock(&rwlock->fallback_.read_mutex_); if (--rwlock->fallback_.num_readers_ == 0) uv_mutex_unlock(&rwlock->fallback_.write_mutex_); uv_mutex_unlock(&rwlock->fallback_.read_mutex_); } 这个逻辑的bug所在: 1、fallback_.write_mutex_和fallback_.read_mutex_都是临界体,windows上临街体是线程相关的锁,获取和释放都必须在同一个线程。。。必须!!! 而上面的逻辑是:当“读计数是0时就解锁之前阻塞的写线程”,“计数变0”时,未必当前线程就是当初获取这个锁的线程,所以不满足临界体的调用要求。 2、严格来说不算bug:每次在获取锁之前,它的代码都是先阻塞所有其他线程(类似uv_mutex_lock(&rwlock->fallback_.read_mutex_);这样的),然后操作相关锁,完了解锁。这个实际上完全可以用原子替代,windows上Interlocked_系列函数,linux上是atom_系列函数。同时,那个计数值也是可以用原子操作替代的,以提高效率。同时说段扯淡的话:鄙人认为一切宣传所谓“跨平台”的程序,都是扯淡,各自都有各自特殊的地方,肯定都得有自己的专门处理,你非得把它们纠结成一个东西。而且对开发人员讲,能同时熟悉多个操作系统平台,几乎没可能,这里的bug就是很明显的例子,libuv库的作者显然对windows系统来说是个半吊子货。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
windows 的临界区获取和释放都必须在同一个线程