多线程的那点儿事(之原子锁)

【 声明:版权所有,欢迎转载,请勿用于商业用途。  联系信箱:feixiaoxing
@163.com】

  
 原子锁是多线程编程中的一个特色。然而,在平时的软件编写中,原子锁的使用并不是很多。这其中原因很多,我想主要有两个方面。第一,关于原子锁这方面的内容介绍的比较少;第二,人们在编程上面习惯于已有的方案,如果没有特别的需求,不过贸然修改已存在的代码。毕竟对很多人来说,不求有功,但求无过。保持当前代码的稳定性还是很重要的。
 
    其实,早在《多线程数据互斥》这篇博客中,我们就已经介绍过原子锁。本篇博客主要讨论的就是原子锁怎么使用。中间的一些用法只是我个人的一些经验,希望能够抛砖引玉,多听听大家的想法。

(1)查找函数中原子锁   

在一些函数当中,有的时候我们需要对满足某种特性的数据进行查找。在传统的单核CPU上,优化的空间比较有限。但是,现在多核CPU已经成了主流配置。所以我们完全可以把这些查找工作分成几个子函数分在几个核上面并行运算。但是,这中间就会涉及到一个问题,那就是对公共数据的访问。传统的访问方式,应该是这样的,

[cpp] view plaincopy

  1. unsigned int count = 0;
  2. int find_data_process()

  3. {

  4. if(/* data meets our standards */){

  5. EnterCriticalSection(&cs);

  6. count ++;

  7. LeaveCriticalSection(&cs);

  8. }

  9. }

我们看到代码中间使用到了锁,那么势必会涉及到系统调用和函数调度。所以,在执行效率上会大打折扣。那么如果使用原子锁呢?

[cpp] view plaincopy

  1. unsigned int count = 0;
  2. int find_data_process()

  3. {

  4. if(/* data meets our standards */){

  5. InterLockedIncrement(&count);

  6. }

  7. }

有兴趣的朋友可以做这样一道题目,查看0~0xFFFFFFFF上有多少数可以被3整除?大家也可以验证一下用原子锁代替临界区之后,代码的效率究竟可以提高多少。关于多核多线程的编程,朋友们可以参考《多线程基础篇》这篇博客。

(2)代码段中的原子锁
  
 上面的范例只是介绍了统计功能中的原子锁。那么怎么用原子锁代替传统的系统锁呢?比如说,假设原来的数据访问是这样的,

[cpp] view plaincopy

  1. void data_process()

  2. {

  3. EnterCriticalSection(&cs);

  4. do_something();

  5. LeaveCriticalSection(&cs);

  6. }

如果改成原子锁呢,会是什么样的呢?

[cpp] view plaincopy

  1. unsigned int lock = 0;
  2. void data_process()

  3. {

  4. while(1 == InterLockedCompareExchange(&lock, 1, 0));

  5. do_something();

  6. lock = 0;

  7. }

这里用原子锁代替普通的系统锁,完成的功能其实是一样的。那么这中间有什么区别呢?其实,关键要看do_something要执行多久。打个比方来说,现在我们去买包子,但是买包子的人很多。那怎么办呢?有两个选择,如果卖包子的人手脚麻利,服务一个顾客只要10秒钟,那么即使前面排队的有50个人,我们只要等7、8分钟就可以,这点等的时间还是值得的;但是如果不幸这个卖包子的老板服务一个顾客要1分钟,那就悲催了,假使前面有50个人,那我们就要等50多分钟了。50分钟对我们来说可是不短的一个时间,我们完全可以利用这个时间去买点水果,交交水电费什么的,过了这个时间点再来买包子也不迟。

和上面的例子一样,忙等的方法就是原子锁,过一会再来的方法就是哪个传统的系统锁。用哪个,就看这个do_something的时间值不值得我们等待了。

时间: 2024-09-30 10:35:53

多线程的那点儿事(之原子锁)的相关文章

多线程的那点儿事(之生产者-消费者)

生产者-消费者是很有意思的一种算法.它的存在主要是两个目的,第一就是满足生产者对资源的不断创造:第二就是满足消费者对资源的不断索取.当然,因为空间是有限的,所以资源既不能无限存储,也不能无限索取. 生产者的算法, [cpp] view plaincopy WaitForSingleObject(hEmpty, INFINITE); WaitForSingleObject(hMutex, INIFINITE); /* produce new resources */ ReleaseMutex(hM

多线程的那点儿事(基础篇)

多线程编程是现代软件技术中很重要的一个环节.要弄懂多线程,这就要牵涉到多进程?当然,要了解到多进程,就要涉及到操作系统.不过大家也不要紧张,听我慢慢道来.这其中的环节其实并不复杂. (1)单CPU下的多线程 在没有出现多核CPU之前,我们的计算资源是唯一的.如果系统中有多个任务要处理的话,那么就需要按照某种规则依次调度这些任务进行处理.什么规则呢?可以是一些简单的调度方法,比如说 1)按照优先级调度 2)按照FIFO调度 3)按照时间片调度等等 当然,除了CPU资源之外,系统中还有一些其他的资源

多线程的那点儿事(之windows锁)

在windows系统中,系统本身为我们提供了很多锁.通过这些锁的使用,一方面可以加强我们对锁的认识,另外一方面可以提高代码的性能和健壮性.常用的锁以下四种:临界区,互斥量,信号量,event. (1)临界区 临界区是最简单的一种锁.基本的临界区操作有, [cpp] view plaincopy InitializeCriticalSection EnterCriticalSection LeaveCriticalSection DeleteCriticalSection 如果想要对数据进行互斥操

多线程的那点儿事(之自旋锁)

自旋锁是SMP中经常使用到的一个锁.所谓的smp,就是对称多处理器的意思.在工业用的pcb板上面,特别是服务器上面,一个pcb板有多个cpu是 很正常的事情.这些cpu相互之间是独立运行的,每一个cpu均有自己的调度队列.然而,这些cpu在内存空间上是共享的.举个例子说,假设有一个数据 value = 10,那么这个数据可以被所有的cpu访问.这就是共享内存的本质意义. 我们可以看一段Linux 下的的自旋锁代码(kernel 2.6.23,asm-i386/spinlock.h),就可有清晰的

多线程的那点儿事(之多线程调试)

[ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 软件调试是我们软件开发过程中的重要一课.在前面,我们也讨论过程序调试,比如说这里.今天,我们还可以就软件调试多讲一些内容.比如说条件断点,数据断点,多线程断点等等. [cpp] view plain copy #include <stdio.h> int value = 0; void test() { int total; int index; total = 0; for(index 

多线程的那点儿事(之数据同步)

[ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 多线程创建其实十分简单,在windows系统下面有很多函数可以创建多线程,比如说_beginthread.我们就可以利用它为我们编写一段简单的多线程代码, [cpp] view plaincopy #include <windows.h> #include <process.h> #include <stdio.h> unsigned int value = 0;

多线程编程之无锁队列

关于无锁队列的概念与实现,可以参考博文<无锁队列的实现>,主要涉及到的知识点包括CAS原子操作.无锁队列的链表实现.无锁队列的数组实现以及ABA问题. 下面借鉴了<多线程的那点儿事(之无锁队列)>的代码,说明两个线程(一个添加一个读取数据)之间的无锁队列,可以不借助线程互斥方法就能够达到并行效果.代码如下: #define MAX_NUMBER 1000L #define STATUS int #define OK 0 #define FALSE -1 typedef struct

多线程编程之顺序锁

一.什么是顺序锁 顺序锁对读写锁的一种优化,使用顺序锁时,读不会被写执行单元阻塞(在读写锁中,写操作必须要等所有读操作完成才能进行).也就是说,当向一个临界资源中写入的同时,也可以从此临界资源中读取,即实现同时读写,但是不允许同时写数据.如果读执行单元在读操作期间,写执行单元已经发生了写操作,那么,读执行单元必须重新开始,这样保证了数据的完整性,当然这种可能是微乎其微.顺序锁的性能是非常好的,同时他允许读写同时进行,大大的提高了并发性. 二.顺序锁的缺陷 顺序锁的缺陷在于,互斥访问的资源不能是指

多线程编程之原子锁

在<多线程编程之数据访问互斥>一文中简单介绍了原子锁,这里再详细说一下原子锁的概念和用途. (1)简单数据操作 如果在一个多线程环境下对某个变量进行简单数学运算或者逻辑运算,那么就应该使用原子锁操作.因为,使用临界区.互斥量等线程互斥方式将涉及到很多操作系统调用和函数调用等,效率肯定不如原子操作高.比如有这样一个例子: unsigned int count = 0; int data_process() { if(/* conditions */){ EnterCriticalSection(