锁在多线程应用上非常广泛,虽然这个影响效率,但这也是在不影响计算结果上最直观的方法了。多线程编程主要有四种思路,一种是加锁,一种是无锁式编程,一种是 STM 软件事务内存,一种是使用 Erlang 等函数式编程语言。
无锁式编程在新手社区中广泛使用,比如我入门时写多线程程序从不加锁,并且可以肯定的是十个程序有九个都有问题。假如有代码高手一改常态使用无锁式编程写代码,可能有两个原因,一是故意留bug,二是工作不饱和。为啥这么说呢?因为,加锁麻烦,无锁式编程的设计更为麻烦,往往消费成倍工作量来换取程序稳定性。所以,朋友们,这个能不用尽量少用。
STM 简要介绍下,就是通过代码实现自动控制内存,假如写内存冲突就会回滚到之前状态。我对这个不了解,不做过多评论。
Erlang 等函数式编程语言的设计就是从分布式以及多线程上面考虑的,非常适合用于项目开发。不过有一个问题,就是函数式编程太难了(网上说的,我觉得还好啊,比编程入门轻松多了),可以试着Bing一下这篇文章《函数式编程很难,这正是你要学习它的原因》。函数式编程的代码不像现在面向对象代码这样清晰直观,也不适用于面向对象编程的程序员快速开发新项目,但它确实是多线程编程首选,没有之一。
还有3D游戏中常常用到的大规模并行计算,也就是调用显卡进行计算,行业规范OpenCL,动不动就是几千上万个线程,虽然在大规模计算面前靠谱,但不适用于几个十几个线程的常规软件,并且在显卡中执行的代码不可以直接访问内存,所以只在少部分项目中有用。
除了上面这些之外其实还有一种,只有大神级程序员才能写出来的代码,就比如单线程的 node.js 。 CPU 计算速度如此之快, I/O 操作如此之慢,为何就不能充分运用计算机的这种特性呢? node.js 应运而生。这个虽然牛逼,但也不是一般程序员能写出来的。使用 node.js 也可以实现这样的效果,但不在此次讨论之列。
总的来说,还是锁来的最实在。毕竟数据的正确性与效率的折衷上,还是正确性重要。
由于操作系统以及硬件配置不同,效率测试结果可能与同学们有所区别,但效率比例应该是差不多的。以下我测试了6钟锁,分别执行两个进程,每个进程分别对内存指定位置进行自增一亿次,通过所需时间来判断锁的效率。
1、intel 指令锁
__asm { lock inc t }
约底层的代码总是效率越高,这个没得说。本机测试结果在 2s 左右浮动。不过由于是内联汇编,可能存在兼容问题。
2、Win32 API 自增锁
InterlockedIncrement(&t);
执行时间在 5s 左右浮动。
3、Win32 临界区锁
CRITICAL_SECTION cs = { 0 };//定义临界区所需要使用的变量
InitializeCriticalSection(&cs);//初始化临界区
EnterCriticalSection(&cs);//进入临界区
LeaveCriticalSection(&cs);//退出临界区
DeleteCriticalSection(&cs);//释放临界区
执行时间在 13s 左右浮动。
上面三个为应用层锁,除了这三个外Windows还有内核锁,用于进程间同步。由于内核锁的控制需要在应用层与内核状态间互相切换,导致速度非常慢。在此测试时我将一亿次循环改为了一百万次,执行时间均为 5s 左右,对应上面约为 500s 。
4、Windows 互斥量
HANDLE hMutex = CreateMutex(NULL, FALSE, TEXT("testMutex_"));//创建互斥量
WaitForSingleObject(hMutex, INFINITE);//等待指定互斥量结束
ReleaseMutex(hMutex);//释放互斥量
CloseHandle(hMutex);//关闭互斥量句柄
5、Windows 事件
HANDLE hEvent = CreateEvent(NULL, FALSE, TRUE, TEXT("testEvent_"));//创建事件
WaitForSingleObject(hEvent, INFINITE);//等待事件
SetEvent(hEvent);//设置事件
CloseHandle(hEvent);//关闭句柄
6、Windows 信号量
HANDLE hSema = CreateSemaphore(NULL, 1, 1, TEXT("testSemaphore_"));//创建信号量
WaitForSingleObject(hSema, INFINITE);//等待信号
ReleaseSemaphore(hSema, 1, NULL);//释放信号量
CloseHandle(hSema);//关闭句柄
Windows 信号量主要用于多对象同步互斥,比如5个进程访问3个文件等等。
本来准备试试 boost::thread 和 pthread 的,不过,鉴于跨平台多线程库都有的毛病,暂停都没有,果断不试了。估计效率应该和Windows 临界区差别不大。
各种锁介绍完毕,其中个人感觉在应用层实现的临界区最符合个人思维习惯,在单进程应用中效率也最高,一般小软件最好都使用这种锁。
当然并不是说后面几种锁完全无用,毕竟不同的锁有不同的用武之地,比如跨进程、应用层内核层同步等等,这是应用层锁不能实现的(Windows 的应用层对每个进程分别隔离,进程之间不可互相访问,导致锁不可共用)。
另外,我写了一个小型多线程库,使用临界区,方便Windows下多线程应用开发,具体在http://blog.csdn.net/fawdlstty/article/details/45017217参考。