《深入浅出 Java Concurrency》——原子操作

part1 从AtomicInteger開始

从相对简单的Atomic入手(java.util.concurrent是基于Queue的并发包。而Queue。非常多情况下使用到了Atomic操作。因此首先从这里開始)。非常多情况下我们仅仅是须要一个简单的、高效的、线程安全的递增递减方案。

注意,这里有三个条件:简单,意味着程序猿尽可能少的操作底层或者实现起来要比較easy;高效意味着耗用资源要少。程序处理速度要快。线程安全也非常重要,这个在多线程下能保证数据的正确性。这三个条件看起来比較简单,可是实现起来却难以令人惬意。

通常情况下,在Java里面。++i或者--i不是线程安全的,这里面有三个独立的操作:或者变量当前值,为该值+1/-1,然后写回新的值。在没有额外资源能够利用的情况下。仅仅能使用加锁才干保证读-改-写这三个操作时“原子性”的。

Doug Lea在未将backport-util-concurrent 合并到JSR
166
 里面来之前,是採用纯Java实现的。于是不可避免的採用了synchronizedkeyword。

public final synchronized void set(int newValue);

public final synchronized int getAndSet(int newValue);

public final synchronized int incrementAndGet();

同一时候在变量上使用了volatile (后面会详细来讲volatile究竟是个什么东东)来保证get()的时候不用加锁。

虽然synchronized的代价还是非常高的。可是在没有JNI的手段下纯Java语言还是不能实现此操作的。

JSR 166提上日程后,backport-util-concurrent就合并到JDK 5.0里面了,在这里面反复使用了现代CPU的特性来减少锁的消耗。

后本章的最后小结中会谈到这些原理和特性。在此之前先看看API的使用。

一切从java.util.concurrent.atomic.AtomicInteger開始。

int addAndGet(int delta)

以原子方式将给定值与当前值相加。

实际上就是等于线程安全版本号的i =i+delta操作。

boolean compareAndSet(int expect, int update)

假设当前值 == 预期值,则以原子方式将该值设置为给定的更新值。 假设成功就返回true,否则返回false。而且不改动原值。

int decrementAndGet()

以原子方式将当前值减 1。 相当于线程安全版本号的--i操作。

int get()

获取当前值。

int getAndAdd(int delta)

以原子方式将给定值与当前值相加。 相当于线程安全版本号的t=i;i+=delta;return t;操作。

int getAndDecrement()

以原子方式将当前值减 1。 相当于线程安全版本号的i--操作。

int getAndIncrement()

以原子方式将当前值加 1。 相当于线程安全版本号的i++操作。

int getAndSet(int newValue)

以原子方式设置为给定值,并返回旧值。

相当于线程安全版本号的t=i;i=newValue;return t;操作。

int incrementAndGet()

以原子方式将当前值加 1。 相当于线程安全版本号的++i操作。

void lazySet(int newValue)

最后设置为给定值。 延时设置变量值。这个等价于set()方法,可是因为字段是volatile类型的,因此次字段的改动会比普通字段(非volatile字段)有略微的性能延时(虽然能够忽略),所以假设不是想马上读取设置的新值,同意在“后台”改动值,那么此方法就非常实用。

假设还是难以理解。这里就类似于启动一个后台线程如运行改动新值的任务,原线程就不等待改动结果马上返回(这样的解释事实上是不对的,可是能够这么理解)。

void set(int newValue)

设置为给定值。

直接改动原始值。也就是i=newValue操作。

boolean weakCompareAndSet(int expect, int update)

假设当前值 == 预期值,则以原子方式将该设置为给定的更新值。

JSR规范中说:以原子方式读取和有条件地写入变量但   创建不论什么 happen-before 排序,因此不提供与除  weakCompareAndSet 目标外不论什么变量曾经或兴许读取或写入操作有关的不论什么保证。大意就是说调用weakCompareAndSet时并不能保证不存在happen-before的发生(也就是可能存在指令重排序导致此操作失败)。可是从Java源代码来看。事实上此方法并没有实现JSR规范的要求。最后效果和compareAndSet是等效的。都调用了unsafe.compareAndSwapInt()完毕操作。

以下的代码是一个測试例子,为了省事就写在一个方法里面来了。

 package  xylz.study.concurrency.atomic;

 import  java.util.concurrent.atomic.AtomicInteger;

 import  org.junit.Test;

 import   static  org.junit.Assert. * ;

 public   class  AtomicIntegerTest  {

     @Test

      public   void  testAll()  throws  InterruptedException {

          final  AtomicInteger value  =   new  AtomicInteger( 10 );

         assertEquals(value.compareAndSet( 1 ,  2 ),  false );

         assertEquals(value.get(),  10 );

         assertTrue(value.compareAndSet( 10 ,  3 ));

         assertEquals(value.get(),  3 );

         value.set( 0 );

          //

         assertEquals(value.incrementAndGet(),  1 );

         assertEquals(value.getAndAdd( 2 ), 1 );

         assertEquals(value.getAndSet( 5 ), 3 );

         assertEquals(value.get(), 5 );

          //

          final   int  threadSize  =   10 ;

         Thread[] ts  =   new  Thread[threadSize];

          for  ( int  i  =   0 ; i  <  threadSize; i ++ )  {

             ts[i]  =   new  Thread()  {

                  public   void  run()  {

                     value.incrementAndGet();

                 }

             } ;

         }

          //

          for (Thread t:ts)  {

             t.start();

         }

          for (Thread t:ts)  {

             t.join();

         }

          //

         assertEquals(value.get(),  5 + threadSize);

     }

 }

因为这里样例比較简单。这里就不做过多介绍了。

AtomicInteger和AtomicLong、AtomicBoolean、AtomicReference几乎相同。这里就不介绍了。在下一篇中就介绍下数组、字段等其它方面的原子操作。

參考资料:

(1)http://stackoverflow.com/questions/2443239/java-atomicinteger-what-are-the-differences-between-compareandset-and-weakcompar

(2)http://stackoverflow.com/questions/1468007/atomicinteger-lazyset-and-set

part 2 数组、引用的原子操作

在这一部分開始讨论数组原子操作和一些其它的原子操作。

AtomicIntegerArray/AtomicLongArray/AtomicReferenceArray 的API类似,选择有代表性的AtomicIntegerArray来描写叙述这些问题。

int get(int i)

获取位置  i   的当前值。非常显然,因为这个是数组操作,就有索引越界的问题(IndexOutOfBoundsException异常)。

对于以下的API起始和AtomicInteger是类似的,这样的通过方法、參数的名称就行得到函数意义的写法是很值得称赞的。在《重构:改善既有代码的设计》 和《代码整洁之道》 中都很推崇这样的做法。

void set(int i, int newValue)

void lazySet(int i, int newValue)

int getAndSet(int i, int newValue)

boolean compareAndSet(int i, int expect, int update)

boolean weakCompareAndSet(int i, int expect, int update)

int getAndIncrement(int i)

int getAndDecrement(int i)

int getAndAdd(int i, int delta)

int incrementAndGet(int i)

int decrementAndGet(int i)

int addAndGet(int i, int delta)

总体来说,数组的原子操作在理解上还是相对照较easy的。这些API就是有多使用才干体会到它们的优点,而不不过停留在理论阶段。

如今关注字段的原子更新。

AtomicIntegerFieldUpdater<T>/AtomicLongFieldUpdater<T>/AtomicReferenceFieldUpdater<T,V> 是基于反射的原子更新字段的值。

对应的API也是很easy的,可是也是有一些约束的。

(1)字段必须是volatile类型的。在后面的章节中会具体说明为什么必须是volatile。volatile究竟是个什么东西。

(2)字段的描写叙述类型(修饰符public/protected/default/private)是与调用者与操作对象字段的关系一致。

也就是说调用者能够直接操作对象字段,那么就能够反射进行原子操作。可是对于父类的字段,子类是不能直接操作的,虽然子类能够訪问父类的字段。

(3)仅仅能是实例变量,不能是类变量。也就是说不能加statickeyword。

(4)仅仅能是可改动变量。不能使final变量,由于final的语义就是不可改动。

实际上final的语义和volatile是有冲突的,这两个keyword不能同一时候存在。

(5)对于AtomicIntegerFieldUpdater 和AtomicLongFieldUpdater 仅仅能改动int/long类型的字段。不能改动其包装类型(Integer/Long)。

假设要改动包装类型就须要使用AtomicReferenceFieldUpdater 。

在以下的样例中描写叙述了操作的方法。

 package   xylz.study.concurrency.atomic;

 import   java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

 public   class   AtomicIntegerFieldUpdaterDemo  {

     class   DemoData {

         public   volatile   int   value1  =   1 ;

         volatile   int   value2  =   2 ;

         protected   volatile   int   value3  =   3 ;

         private   volatile   int   value4  =   4 ;

    }

     AtomicIntegerFieldUpdater < DemoData >   getUpdater(String
fieldName)  {

          return   AtomicIntegerFieldUpdater.newUpdater(DemoData. class ,
fieldName);

     }

      void   doit()  {

         DemoData data  =   new   DemoData();

         System.out.println( " 1 ==>  " + getUpdater( " value1 " ).getAndSet(data,  10 ));

         System.out.println( " 3 ==>  " + getUpdater( " value2 " ).incrementAndGet(data));

         System.out.println( " 2 ==>  " + getUpdater( " value3 " ).decrementAndGet(data));

         System.out.println( " true ==>  " + getUpdater( " value4 " ).compareAndSet(data,  4 ,  5 ));

     }

      public   static   void   main(String[]
args)  {

         AtomicIntegerFieldUpdaterDemo demo  =   new   AtomicIntegerFieldUpdaterDemo();

         demo.doit();

     }

 }

在上面的样例中DemoData的字段value3/value4对于AtomicIntegerFieldUpdaterDemo类是不可见的。因此通过反射是不能直接改动其值的。

AtomicMarkableReference 类描写叙述的一个<Object,Boolean>的对,可以原子的改动Object或者Boolean的值。这样的数据结构在一些缓存或者状态描写叙述中比較实用。这样的结构在单个或者同一时候改动Object/Boolean的时候可以有效的提高吞吐量。

AtomicStampedReference 类维护带有整数“标志”的对象引用,能够用原子方式对其进行更新。

对照AtomicMarkableReference 类的<Object,Boolean>,AtomicStampedReference 维护的是一种类似<Object,int>的数据结构,事实上就是对对象(引用)的一个并发计数。

可是与AtomicInteger 不同的是,此数据结构能够携带一个对象引用(Object)。而且能够对此对象和计数同一时候进行原子操作。

在后面的章节中会提到“ABA问题”,而AtomicMarkableReference/ AtomicStampedReference 在解决“ABA问题”上非常实用

part3 指令重排序与happens-before法则

在这个小结里面重点讨论原子操作的原理和设计思想。

因为在下一个章节中会谈到锁机制,因此此小节中会适当引入锁的概念。

Java Concurrency in Practice 中是这样定义线程安全的:

当多个线程訪问一个类时,假设不用考虑这些线程在执行时 环境下的调度和交替执行,而且不须要额外的同步及在调用方代码不必做其它的协调 ,这个类的行为仍然是正确的 ,那么这个类就是线程安全的。

显然仅仅有资源竞争时才会导致线程不安全,因此无状态对象永远是线程安全的 

原子操作的描写叙述是: 多个线程运行一个操作时,当中不论什么一个线程要么全然运行完此操作,要么没有运行此操作的不论什么步骤 。那么这个操作就是原子的。

枯燥的定义介绍完了,以下说更枯燥的理论知识。

指令重排序

Java语言规范规定了JVM线程内部维持顺序化语义,也就是说仅仅要程序的终于结果等同于它在严格的顺序化环境下的结果,那么指令的运行顺序就可能与代码的顺序不一致。这个过程通过叫做指令的重排序。

指令重排序存在的意义在于:JVM可以依据处理器的特性(CPU的多级缓存系统、多核处理器等)适当的又一次排序机器指令,使机器指令更符合CPU的运行特点,最大限度的发挥机器的性能。

程序运行最简单的模型是依照指令出现的顺序运行,这样就与运行指令的CPU无关,最大限度的保证了指令的可移植性。

这个模型的专业术语叫做顺序化一致性模型。可是现代计算机体系和处理器架构都不保证这一点(由于人为的指定并不能总是保证符合CPU处理的特性)。

我们来看最经典的一个案例。

 package   xylz.study.concurrency.atomic;

 public   class   ReorderingDemo  {

      static   int   x  =   0 , y  =   0 , a  =   0 , b  =   0 ;

      public   static   void   main(String[]
args)  throws   Exception  {

          for   ( int   i  =   0 ;
i  <   100 ; i ++ )  {

             x = y = a = b = 0 ;

             Thread one  =   new   Thread()  {

                  public   void   run()  {

                     a  =   1 ;

                     x  =   b;

                 }

             } ;

             Thread two  =   new   Thread()  {

                  public   void   run()  {

                     b  =   1 ;

                     y  =   a;

                 }

             } ;

             one.start();

             two.start();

             one.join();

             two.join();

             System.out.println(x  +   "   "   +   y);

         }

     }

 }

在这个样例中one/two两个线程改动区x,y,a,b四个变量。在运行100次的情况下。可能得到(0 1)或者(1 0)或者(1 1)。其实依照JVM的规范以及CPU的特性有非常可能得到(0 0)。当然上面的代码大家不一定能得到(0 0),因为run()里面的操作过于简单,可能比启动一个线程花费的时间还少。因此上面的样例难以出现(0,0)。可是在现代CPU和JVM上确实是存在的。因为run()里面的动作对于结果是无关的,因此里面的指令可能发生指令重排序,即使是依照程序的顺序运行,数据变化刷新到主存也是须要时间的。

假定是依照a=1;x=b;b=1;y=a;运行的,x=0是比較正常的,尽管a=1在y=a之前运行的,可是因为线程one运行a=1完毕后还没有来得及将数据1写回主存(这时候数据是在线程one的堆栈里面的),线程two从主存中拿到的数据a可能仍然是0(显然是一个过期数据,可是是有可能的)。这样就发生了数据错误。

在两个线程交替运行的情况下数据的结果就不确定了,在机器压力大。多核CPU并发运行的情况下,数据的结果就更加不确定了。

Happens-before法则

Java存储模型有一个happens-before原则。就是假设动作B要看到动作A的运行结果(不管A/B是否在同一个线程里面运行)。那么A/B就须要满足happens-before关系。

在介绍happens-before法则之前介绍一个概念:JMM动作(Java Memeory Model Action),Java存储模型动作。一个动作(Action)包含:变量的读写、监视器加锁和释放锁、线程的start()和join()。后面还会提到锁的的。

happens-before完整规则:

(1)同一个线程中的每一个Action都happens-before于出如今其后的不论什么一个Action。

(2)对一个监视器的解锁happens-before于每个兴许对同一个监视器的加锁。

(3)对volatile字段的写入操作happens-before于每个兴许的同一个字段的读操作。

(4)Thread.start()的调用会happens-before于启动线程里面的动作。

(5)Thread中的全部动作都happens-before于其它线程检查到此线程结束或者Thread.join()中返回或者Thread.isAlive()==false。

(6)一个线程A调用还有一个还有一个线程B的interrupt()都happens-before于线程A发现B被A中断(B抛出异常或者A检測到B的isInterrupted()或者interrupted())。

(7)一个对象构造函数的结束happens-before与该对象的finalizer的開始

(8)假设A动作happens-before于B动作。而B动作happens-before与C动作,那么A动作happens-before于C动作。

volatile语义

到眼下为止,我们多次提到volatile。可是却仍然没有理解volatile的语义。

volatile相当于synchronized的弱实现,也就是说volatile实现了类似synchronized的语义,却又没有锁机制。它确保对volatile字段的更新以可预见的方式告知其它的线程。

volatile包括下面语义:

(1)Java 存储模型不会对valatile指令的操作进行重排序:这个保证对volatile变量的操作时依照指令的出现顺序运行的。

(2)volatile变量不会被缓存在寄存器中(仅仅有拥有线程可见)或者其他对CPU不可见的地方。每次总是从主存中读取volatile变量的结果。

也就是说对于volatile变量的改动,其他线程总是可见的。而且不是使用自己线程栈内部的变量。也就是在happens-before法则中,对一个valatile变量的写操作后,其后的不论什么读操作理解可见此写操作的结果。

虽然volatile变量的特性不错。可是volatile并不能保证线程安全的。也就是说volatile字段的操作不是原子性的,volatile变量仅仅能保证可见性(一个线程改动后其他线程可以理解看到此变化后的结果),要想保证原子性,眼下为止仅仅能加锁。

volatile通常在以下的场景:

 volatile   boolean   done  =   false ;

 …

      while (  !   done
) {

         dosomething();

     }

应用volatile变量的三个原则:

(1)写入变量不依赖此变量的值,或者仅仅有一个线程改动此变量

(2)变量的状态不须要与其他变量共同參与不变约束

(3)訪问变量不须要加锁

这一节理论知识比較多。可是这是非常面非常多章节的基础,在后面的章节中会多次提到这些特性。

本小节中还是没有谈到原子操作的原理和思想,在下一节中将依据上面的一些知识来介绍原子操作。

參考资料:

(1)Java Concurrency in Practice

(2)正确使用 Volatile 变量

part 4 CAS操作

在JDK 5之前Java语言是靠synchronizedkeyword保证同步的。这会导致有锁(后面的章节还会谈到锁)。

锁机制存在下面问题:

(1)在多线程竞争下,加锁、释放锁会导致比較多的上下文切换和调度延时,引起性能问题。

(2)一个线程持有锁会导致其他全部须要此锁的线程挂起。

(3)假设一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。

volatile是不错的机制。可是volatile不能保证原子性。因此对于同步终于还是要回到锁机制上来。

独占锁是一种悲观锁,synchronized就是一种独占锁。会导致其他全部须要锁的线程挂起,等待持有锁的线程释放锁。

而还有一个更加有效的锁就是乐观锁。所谓乐观锁就是。每次不加锁而是如果没有冲突而去完毕某项操作。如果由于冲突失败就重试。直到成功为止。

CAS 操作

上面的乐观锁用到的机制就是CAS,Compare and Swap。

CAS有3个操作数。内存值V,旧的预期值A,要改动的新值B。

当且仅当预期值A和内存值V同样时,将内存值V改动为B,否则什么都不做。

非堵塞算法 (nonblocking algorithms)

一个线程的失败或者挂起不应该影响其它线程的失败或挂起的算法。

现代的CPU提供了特殊的指令。可以自己主动更新共享数据,并且可以检測到其它线程的干扰,而 compareAndSet() 就用这些取代了锁定。

拿出AtomicInteger来研究在没有锁的情况下是怎样做到数据正确性的。

private volatile int value;

首先毫无以为,在没有锁的机制下可能须要借助volatile原语,保证线程间的数据是可见的(共享的)。

这样才获取变量的值的时候才干直接读取。

public final int get() {

return value;

}

然后来看看++i是怎么做到的。

public final int incrementAndGet() {

for (;;) {

int current = get();

int next = current + 1;

if (compareAndSet(current, next))

return next;

}

}

在这里採用了CAS操作。每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作。假设成功就返回结果,否则重试直到成功为止。

而compareAndSet利用JNI来完毕CPU指令的操作。

public final boolean compareAndSet(int expect, int update) {

return unsafe.compareAndSwapInt(this, valueOffset, expect, update);

}

总体的过程就是这样子的。利用CPU的CAS指令,同一时候借助JNI来完毕Java的非堵塞算法。其他原子操作都是利用类似的特性完毕的。

而整个J.U.C都是建立在CAS之上的。因此对于synchronized堵塞算法,J.U.C在性能上有了非常大的提升。參考资料的文章中介绍了假设利用CAS构建非堵塞计数器、队列等数据结构。

CAS看起来非常爽,可是会导致“ABA问题”。

CAS算法实现一个重要前提须要取出内存中某时刻的数据,而在下时刻比較并替换,那么在这个时间差类会导致数据的变化。

比方说一个线程one从内存位置V中取出A。这时候还有一个线程two也从内存中取出A,而且two进行了一些操作变成了B。然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A。然后one操作成功。虽然线程one的CAS操作成功。可是不代表这个过程就是没有问题的。

假设链表的头在变化了两次后恢复了原值,可是不代表链表就没有变化。因此前面提到的原子操作AtomicStampedReference/AtomicMarkableReference就非常实用了。

这同意一对变化的元素进行原子操作。

參考资料:

(1)非堵塞算法简单介绍

(2)流行的原子

转自:http://www.blogjava.net/xylz/archive/2010/07/04/325206.html

时间: 2024-10-11 03:35:20

《深入浅出 Java Concurrency》——原子操作的相关文章

第3章 文件I/O(3)_内核数据结构、原子操作

3. 文件I/O的内核数据结构 (1) 内核数据结构表 数据结构 主要成员 文件描述符表 ①文件描述符标志 ②文件表项指针 文件表项 ①文件状态标志(读.写.追加.同步和非阻塞等状态标志) ②当前文件偏移量 ③i节点表项指针 ④引用计数器 i节点 ①文件类型和对该文件的操作函数指针 ②当前文件长度 ③文件所有者 ④文件所在设备.文件访问权限 ⑤指向文件数据在磁盘块上所在位置的指针等. (2)3张表的关系 4. 文件的原子操作 (1)文件追加 ①打开文件时使用O_APPEND标志,进程对文件偏移量

C++拾遗--多线程:原子操作解决线程冲突

C++拾遗--多线程:原子操作解决线程冲突 前言 在多线程中操作全局变量一般都会引起线程冲突,为了解决线程冲突,引入原子操作. 正文 1.线程冲突 #include <stdio.h> #include <stdlib.h> #include <process.h> #include <Windows.h> int g_count = 0; void count(void *p) { Sleep(100); //do some work //每个线程把g_c

多线程编程之原子操作

在多线程环境中,对共享的变量的访问,可以使用基于Compare And Swap这种lock free的技术进行实现,这种实现的好处是效率高. 一.原子操作摘录 1.1 Android 源码:system/core/libcutils /atomic.c(针对X86): 1 #elif defined(__i386__) || defined(__x86_64__) 2 3 void android_atomic_write(int32_t value, volatile int32_t* ad

原子操作的原理

1. 引言 原子(atom)本意是“不能被进一步分割的最小粒子”,而原子操作(atomic operation)意为"不可被中断的一个或一系列操作" .在多处理器上实现原子操作就变得有点复杂.本文让我们一起来聊一聊在Intel处理器和Java里是如何实现原子操作的. 2. 术语定义 术语 英文 解释 缓存行 Cache line 缓存的最小操作单位 比较并交换 Compare and Swap CAS操作需要输入两个数值,一个旧值(期望操作前的值)和一个新值,在操作期间先比较下旧值有没

再探c++11 Thread库之原子操作

我在之前一篇博文<初探c++11 Thread库之使写多线程程序>中,着重介绍了<thread>头文件中的std::thread类以及其上的一些基本操作,至此我们动手写多线程程序已经基本没有问题了.但是,单线程的那些"坑"我们仍还不知道怎么去避免. 多线程存在的问题 多线程最主要的问题就是共享数据带来的问题.如果共享数据都是只读的,那么没问题,因为只读操作不会影响到数据,更不会涉及对数据的修改,所以所有线程都会获得同样的数据.但是,当一个或多个线程要修改共享数据

UNIX高级环境编程(2)FIle I/O - 原子操作、共享文件描述符和I/O控制函数

引言: 本篇通过对open函数的讨论,引入原子操作,多进程通信(共享文件描述符)和内核相关的数据结构. 还会讨论集中常见的文件IO控制函数,包括: dup和dup2 sync,fsync和fdatasync fcntl ioctl /dev/fd ? 一.文件共享 这里所说的文件共享主要指的是进程间共享打开的文件. 这一节主要讨论文件在进程间共享的理论基础和数据结构,不涉及具体的技术实现,不同的系统可能会有不同的实现. 每一个打开的文件,涉及内核中的三种数据结构,这三种数据结构也是文件在进程间共

原子操作&amp;优化和内存屏障

原子操作 假定运行在两个CPU上的两个内核控制路径试图执行非原子操作同时"读-修改-写"同一存储器单元.首先,两个CPU都试图读同一单元,但是存储器仲裁器插手,只允许其中的一个访问而让另一个延迟.然而,当第一个读操作已经完成后,延迟的CPU从那个存储器单元正好读到同一个(旧)值.然后,两个CPU都试图向那个存储器单元写一新值,总线存储器访问再一次被存储器仲裁器串行化,最终,两个写操作都成功.但是,全局的结果是不对的,因为两个CPU写入同一(新)值.因此,两个交错的"读-修改-

[OS] 多线程--原子操作 Interlocked系列函数

转自:http://blog.csdn.net/morewindows/article/details/7429155 上一篇<多线程--第一次亲密接触 CreateThread与_beginthreadex本质区别>中讲到一个多线程报数功能.为了描述方便和代码简洁起见,我们可以只输出最后的报数结果来观察程序是否运行出错.这也非常类似于统计一个网站每天有多少用户登录,每个用户登录用一个线程模拟,线程运行时会将一个表示计数的变量递增.程序在最后输出计数的值表示有今天多少个用户登录,如果这个值不等

C++11开发中的Atomic原子操作

C++11开发中的Atomic原子操作 Nicol的博客铭 原文  https://taozj.org/2016/09/C-11%E5%BC%80%E5%8F%91%E4%B8%AD%E7%9A%84Atomic%E5%8E%9F%E5%AD%90%E6%93%8D%E4%BD%9C/ 主题 C++ 原子操作在多线程开发中经常用到,比如在计数器,序列产生器等地方,这类情况下数据有并发的危险,但是用锁去保护又显得有些浪费,所以原子类型操作十分的方便. 原子操作虽然用起来简单,但是其背景远比我们想象

单核,多核CPU的原子操作

一. 何谓"原子操作":原子操作就是: 不可中断的一个或者一系列操作, 也就是不会被线程调度机制打断的操作, 运行期间不会有任何的上下文切换(context switch). 二. 为什么关注原子操作?1. 如果确定某个操作是原子的, 就不用为了去保护这个操作而加上会耗费昂贵性能开销的锁. - (巧妙的利用原子操作和实现无锁编程)2. 借助原子操作可以实现互斥锁(mutex). (linux中的mutex_lock_t)3. 借助互斥锁, 可以实现让更多的操作变成原子操作. 三. 单核