精华内容
下载资源
问答
  • 多线程原子操作
    千次阅读
    2019-03-06 14:51:11

    “原子操作(atomic operation)是不需要synchronized”,这是多线程编程的老生常谈了。所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切 [1] 换到另一个线程)。

    更多相关内容
  • C++多线程原子操作

    2014-07-03 08:39:18
    C++多线程原子操作实现方法。很很详解!
  • c++多线程原子操作

    千次阅读 2022-03-14 17:04:21
    原来会分为好几个指令,由于多线程程序中,可能会存在数据竞争,可能导致对变量的操作不是原子性,那么从编译实现角度,将原本几个指令的操作变成一个指令来执行,可以在无锁情况下实现原子操作,从而避免数据竞争。...

    1、声明头文件

    #include <atomic>         // std::atomic, std::atomic_flag, ATOMIC_FLAG_INIT
    

    原子操作:
    对一个变量的访问操作,原来会分为好几个指令,由于多线程程序中,可能会存在数据竞争,可能导致对变量的操作不是原子性,那么从编译实现角度,将原本几个指令的操作变成一个指令来执行,可以在无锁情况下实现原子操作,从而避免数据竞争。

    std::atomic::atomic 
    default (1)	
    atomic() noexcept = default;
    initialization (2)	
    constexpr atomic (T val) noexcept;
    copy [deleted] (3)	
    atomic (const atomic&) = delete;
    Construct atomic
    Constructs an atomic object:
    
    (1) default constructor
    Leaves the atomic object in an uninitialized state.
    An uninitialized atomic object may later be initialized by calling atomic_init.
    (2) initialization constructor
    Initializes the object with val.
    (3) copy construction
    Deleted (atomic objects cannot be copied/moved).
    

    注意:
    atomic不可拷贝和移动

    2、函数

    atomic::store

    Modify contained value (public member function )
    原子地修改容器中的值

    atomic::load

    Read contained value (public member function )
    原子地读取容器中的值

    std::atomic::compare_exchange_strong

    bool compare_exchange_weak( T& expected, T desired,std::memory_order success,std::memory_order failure );
    
    bool compare_exchange_weak( T& expected, T desired,std::memory_order success,std::memory_order failure ) volatile;
    
    bool compare_exchange_weak( T& expected, T desired,std::memory_order order = std::memory_order_seq_cst );
    
    bool compare_exchange_weak( T& expected, T desired,std::memory_order order = std::memory_order_seq_cst ) volatile;
    
    bool compare_exchange_strong( T& expected, T desired,std::memory_order success,std::memory_order failure );
    
    bool compare_exchange_strong( T& expected, T desired,std::memory_order success,std::memory_order failure ) volatile;
    
    bool compare_exchange_strong( T& expected, T desired,std::memory_order order = std::memory_order_seq_cst );
    
    bool compare_exchange_strong( T& expected, T desired,std::memory_order order =std::memory_order_seq_cst ) volatile;
    

    expected-reference to the value expected to be found in the atomic object

    desired-the value to store in the atomic object if it is as expected

    success-the memory synchronization ordering for the read-modify-write operation if the comparison succeeds. All values are permitted.

    failure-the memory synchronization ordering for the load operation if the comparison fails. Cannot be std::memory_order_release or std::memory_order_acq_rel and cannot specify stronger ordering than success (until C++17)

    order-the memory synchronization ordering for both operations

    返回值:true如果成功地更改了基础原子值,false否则。

    3、使用

    std::atomic<int>  ai;
    ai.store(3);
    // tst_val != ai   ==>  tst_val is modified
    exchanged= ai.compare_exchange_strong( tst_val, new_val );
    // tst_val == ai   ==>  ai is modified
    exchanged= ai.compare_exchange_strong( tst_val, new_val );  
    
    1. 当ai = 3时,ai不等于 tst_val, 将tst_val 的值设为3,返回false。
    2. 当ai= 3时,ai 等于tst_val, 将tst_val 的值设为new_val, 即将5赋值给ai,返回true。
    展开全文
  • Java多线程原子操作

    千次阅读 2019-06-03 22:11:29
    本文目录: 文章目录CAS原理与问题CAS的操作过程...在并发编程中很容易出现并发安全问题,最简单的例子就是多线程更新变量i=1,多个线程执行i++操作,就有可能获取不到正确的值,而这个问题,最常用的方法是通过Sy...

    本文目录:


    在并发编程中很容易出现并发安全问题,最简单的例子就是多线程更新变量i=1,多个线程执行i++操作,就有可能获取不到正确的值,而这个问题,最常用的方法是通过Synchronized进行控制来达到线程安全的目的。但是由于synchronized是采用的是悲观锁策略,并不是特别高效的一种解决方案。实际上,在J.U.C下的Atomic包提供了一系列的操作简单,性能高效,并能保证线程安全的类去更新多种类型。Atomic包下的这些类都是采用乐观锁策略CAS来更新数据。

    CAS原理与问题

    CAS操作(又称为无锁操作)是一种乐观锁策略。它假设所有线程访问共享资源的时候不会出现冲突,因此不会阻塞其他线程的操作。那么,如果出现冲突了怎么办?无锁操作是使用CAS(compare and swap)来鉴别线程是否出现冲突,出现冲突就重试当前操作直到没有冲突为止。

    CAS的操作过程

    举例说明:
    Atomic包中的AtomicInteger类,是通过Unsafe类下的native函数compareAndSwapInt自旋来保证原子性,
    其中incrementAndGet函数调用的getAndAddInt函数如下所示:

    public final int getAndAddInt(Object var1, long var2, int var4) {
            int var5;
            do {
                var5 = this.getIntVolatile(var1, var2);
            } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    
            return var5;
        }
    

    CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
    可见只有自旋实现更新数据操作之后,while循环才能够结束。

    CAS的问题

    1. 自旋时间过长。由compareAndSwapInt函数可知,自旋时间过长会对性能是很大的消耗。
    2. ABA问题。因为CAS会检查旧值有没有变化,这里存在这样一个有意思的问题。比如一个旧值A变为了成B,然后再变成A,刚好在做CAS时检查发现旧值并没有变化依然为A,但是实际上的确发生了变化。解决方案可以添加一个版本号可以解决。原来的变化路径A->B->A就变成了1A->2B->3C,或使用AtomicStampedReference工具类。

    Atomic包的使用

    原子更新基本类型

    Atomic包中原子更新基本类型的工具类:
    AtomicBoolean:以原子更新的方式更新boolean;
    AtomicInteger:以原子更新的方式更新Integer;
    AtomicLong:以原子更新的方式更新Long;

    这几个类的用法基本一致,这里以AtomicInteger为例总结常用的方法

    1. addAndGet(int delta):以原子方式将输入的数值与实例中原本的值相加,并返回最后的结果;
    2. incrementAndGet() :以原子的方式将实例中的原值进行加1操作,并返回最终相加后的结果;
    3. getAndSet(int newValue):将实例中的值更新为新值,并返回旧值;
    4. getAndIncrement():以原子的方式将实例中的原值加1,返回的是自增前的旧值;

    原理不再赘述,参考上文compareAndSwapInt函数。

    AtomicInteger使用示例:

    public class AtomicExample {
    
        private static AtomicInteger atomicInteger = new AtomicInteger(2);
    
        public static void main(String[] args) {
            System.out.println(atomicInteger.getAndIncrement());
            System.out.println(atomicInteger.incrementAndGet());
            System.out.println(atomicInteger.get());
        }
    }
    // 2 4 4
    

    LongAdder

    为了解决自旋导致的性能问题,JDK8在Atomic包中推出了LongAdder类。LongAdder采用的方法是,共享热点数据分离的计数:将一个数字的值拆分为一个数组。不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多;要得到这个数字的话,就要把这个值加起来。相比AtomicLong,并发量大大提高。

    优点:有很高性能的并发写的能力
    缺点:读取的性能不是很高效,而且如果读取的时候出现并发写的话,结果可能不是正确的

    原子更新数组类型

    Atomic包中提供能原子更新数组中元素的工具类:
    AtomicIntegerArray:原子更新整型数组中的元素;
    AtomicLongArray:原子更新长整型数组中的元素;
    AtomicReferenceArray:原子更新引用类型数组中的元素

    这几个类的用法一致,就以AtomicIntegerArray来总结下常用的方法:

    1. addAndGet(int i, int delta):以原子更新的方式将数组中索引为i的元素与输入值相加;
    2. getAndIncrement(int i):以原子更新的方式将数组中索引为i的元素自增加1;
    3. compareAndSet(int i, int expect, int update):将数组中索引为i的位置的元素进行更新

    AtomicIntegerArray与AtomicInteger的方法基本一致,只不过在前者的方法中会多一个指定数组索引位i。

    AtomicIntegerArray使用示例:

    public class AtomicExample {
    
        private static int[] value = new int[]{1, 2, 3};
        private static AtomicIntegerArray integerArray = new AtomicIntegerArray(value);
    
        public static void main(String[] args) {
            //对数组中索引为2的位置的元素加3
            int result = integerArray.getAndAdd(2, 3);
            System.out.println(integerArray.get(2));
            System.out.println(result);
        }
    }
    // 6 3
    

    原子更新引用类型

    如果需要原子更新引用类型变量的话,为了保证线程安全,Atomic也提供了相关的类:

    1. AtomicReference:原子更新引用类型;
    2. AtomicReferenceFieldUpdater:原子更新引用类型里的字段;
    3. AtomicMarkableReference:原子更新带有标记位的引用类型;

    AtomicReference使用示例:

    public class AtomicExample {
    
        private static AtomicReference<User> reference = new AtomicReference<>();
    
        public static void main(String[] args) {
            User user1 = new User("a", 1);
            reference.set(user1);
            User user2 = new User("b",2);
            User user = reference.getAndSet(user2);
            System.out.println(user);
            System.out.println(reference.get());
        }
    
        static class User {
            private String userName;
            private int age;
    
            public User(String userName, int age) {
                this.userName = userName;
                this.age = age;
            }
    
            @Override
            public String toString() {
                return "User{" +
                        "userName='" + userName + '\'' +
                        ", age=" + age +
                        '}';
            }
        }
    }
    // User{userName='a', age=1}
    // User{userName='b', age=2}
    

    AtomicReferenceFieldUpdater使用示例:

    public class AtomicExample {
    
        public static void main(String[] args) {
            AtomicReferenceFieldUpdater updater = AtomicReferenceFieldUpdater.newUpdater(Dog.class, String.class, "name");
            Dog dog1 = new Dog();
            updater.compareAndSet(dog1, dog1.name, "cat");
            System.out.println(dog1.name);
        }
    }
    
    class Dog {
        volatile String name = "dog1";
    }
    

    原子更新字段类型

    如果需要更新对象的某个字段,Atomic同样也提供了相应的原子操作类:

    1. AtomicIntegeFieldUpdater:原子更新整型字段类;
    2. AtomicLongFieldUpdater:原子更新长整型字段类;

    要想使用原子更新字段需要两步操作:
    原子更新字段类型类都是抽象类,只能通过静态方法newUpdater来创建一个更新器,并且需要设置想要更新的类和属性;
    更新类的属性必须使用public volatile进行修饰;

    AtomicIntegerFieldUpdater使用示例:

    public class AtomicExample {
    
        private static AtomicIntegerFieldUpdater updater = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
    
        public static void main(String[] args) {
            User user = new User("a", 1);
            System.out.println(updater.getAndAdd(user, 5));
            System.out.println(updater.addAndGet(user, 1));
            System.out.println(updater.get(user));
        }
    
        static class User {
            private String userName;
            public volatile int age;
    
            public User(String userName, int age) {
                this.userName = userName;
                this.age = age;
            }
    
            @Override
            public String toString() {
                return "User{" +
                        "userName='" + userName + '\'' +
                        ", age=" + age +
                        '}';
            }
        }
    }
    

    解决CAS的ABA问题

    AtomicStampedReference:原子更新引用类型,这种更新方式会带有版本号,从而解决CAS的ABA问题

    AtomicStampedReference使用示例:

    public class AtomicExample {
    
        public static void main(String[] args) {
            Integer init1 = 1110;
    //        Integer init2 = 126;
            AtomicStampedReference<Integer> reference = new AtomicStampedReference<>(init1, 1);
            int curent1 = reference.getReference();
    //        Integer current2 = reference.getReference();
            reference.compareAndSet(reference.getReference(), reference.getReference() + 1, reference.getStamp(), reference.getStamp() + 1);//正确写法
    //        reference.compareAndSet(current2, current2+1, reference.getStamp(), reference.getStamp() + 1);//正确写法
    //        reference.compareAndSet(1110, 1111, reference.getStamp(), reference.getStamp() + 1);//错误写法
    //        reference.compareAndSet(curent1, curent1+1, reference.getStamp(), reference.getStamp() + 1);//错误写法
    //        reference.compareAndSet(current2, current2 + 1, reference.getStamp(), reference.getStamp() + 1);
            System.out.println("reference.getReference() = " + reference.getReference());
        }
    }
    

    AtomicStampedReference踩过的坑

    参考上面的代码,分享一个笔者遇到的一次坑。AtomicStampedReference的compareAndSet函数中,前两个参数是使用包装类的。所以当参数超过128时,而且传入参数并不是reference.getReference()获取的话,会导致expectedReference == current.reference为false,则无法进行更新。

    public boolean compareAndSet(V   expectedReference,
                                     V   newReference,
                                     int expectedStamp,
                                     int newStamp) {
            Pair<V> current = pair;
            return
                expectedReference == current.reference &&
                expectedStamp == current.stamp &&
                ((newReference == current.reference &&
                  newStamp == current.stamp) ||
                 casPair(current, Pair.of(newReference, newStamp)));
        }
    

    哎呀,如果我的名片丢了。微信搜索“全菜工程师小辉”,依然可以找到我

    展开全文
  • C++多线程并发(五)---原子操作与无锁编程

    万次阅读 多人点赞 2019-05-12 13:21:23
    一、何为原子操作 前面介绍了多线程间是通过互斥锁与条件变量来保证共享数据的同步的,互斥锁主要是针对过程加锁来实现对共享资源的排他性访问。很多时候,对共享资源的访问主要是对某一数据结构的读写操作,如果...

    一、何为原子操作

    前面介绍了多线程间是通过互斥锁与条件变量来保证共享数据的同步的,互斥锁主要是针对过程加锁来实现对共享资源的排他性访问。很多时候,对共享资源的访问主要是对某一数据结构的读写操作,如果数据结构本身就带有排他性访问的特性,也就相当于该数据结构自带一个细粒度的锁,对该数据结构的并发访问就能更加简单高效,这就是C++11提供的原子数据类型< atomic >。下面解释两个概念:

    • 原子操作:顾名思义就是不可分割的操作,该操作只存在未开始和已完成两种状态,不存在中间状态;
    • 原子类型:原子库中定义的数据类型,对这些类型的所有操作都是原子的,包括通过原子类模板std::atomic< T >实例化的数据类型,也都是支持原子操作的。

    二、如何使用原子类型

    2.1 原子库atomic支持的原子操作

    原子库< atomic >中提供了一些基本原子类型,也可以通过原子类模板实例化一个原子对象,下面列出一些基本原子类型及相应的特化模板如下:
    原子类型
    对原子类型的访问,最主要的就是读和写,但原子库提供的对应原子操作是load()与store(val)。原子类型支持的原子操作如下:
    原子操作

    2.2 原子操作中的内存访问模型

    原子操作保证了对数据的访问只有未开始和已完成两种状态,不会访问到中间状态,但我们访问数据一般是需要特定顺序的,比如想读取写入后的最新数据,原子操作函数是支持控制读写顺序的,即带有一个数据同步内存模型参数std::memory_order,用于对同一时间的读写操作进行排序。C++11定义的6种类型如下:

    • memory_order_relaxed: 宽松操作,没有同步或顺序制约,仅对此操作要求原子性;
    • memory_order_release & memory_order_acquire: 两个线程A&B,A线程Release后,B线程Acquire能保证一定读到的是最新被修改过的值;这种模型更强大的地方在于它能保证发生在A-Release前的所有写操作,在B-Acquire后都能读到最新值;
    • memory_order_release & memory_order_consume: 上一个模型的同步是针对所有对象的,这种模型只针对依赖于该操作涉及的对象:比如这个操作发生在变量a上,而s = a + b; 那s依赖于a,但b不依赖于a; 当然这里也有循环依赖的问题,例如:t = s + 1,因为s依赖于a,那t其实也是依赖于a的;
    • memory_order_seq_cst: 顺序一致性模型,这是C++11原子操作的默认模型;大概行为为对每一个变量都进行Release-Acquire操作,当然这也是一个最慢的同步模型;

    内存访问模型属于比较底层的控制接口,如果对编译原理和CPU指令执行过程不了解的话,容易引入bug。内存模型不是本章重点,这里不再展开介绍,后续的代码都使用默认的顺序一致性模型或比较稳妥的Release-Acquire模型,如果想了解更多,可以参考链接: C++11 Memory Order

    2.3 使用原子类型替代互斥锁编程

    为便于比较,直接基于前篇文章:线程同步之互斥锁中的示例程序进行修改,用原子库取代互斥库的代码如下:

    //atomic1.cpp 使用原子库取代互斥库实现线程同步
    
    #include <chrono>
    #include <atomic>
    #include <thread>
    #include <iostream> 
    
    std::chrono::milliseconds interval(100);
    
    std::atomic<bool> readyFlag(false);     //原子布尔类型,取代互斥量
    std::atomic<int> job_shared(0); //两个线程都能修改'job_shared',将该变量特化为原子类型
    int job_exclusive = 0; //只有一个线程能修改'job_exclusive',不需要保护
    
    //此线程只能修改 'job_shared'
    void job_1()
    {   
        std::this_thread::sleep_for(5 * interval);
        job_shared.fetch_add(1);
        std::cout << "job_1 shared (" << job_shared.load() << ")\n";
        readyFlag.store(true);      //改变布尔标记状态为真
    }
    
    // 此线程能修改'job_shared'和'job_exclusive'
    void job_2()
    {
        while (true) {    //无限循环,直到可访问并修改'job_shared'
            if (readyFlag.load()) {     //判断布尔标记状态是否为真,为真则修改‘job_shared’
                job_shared.fetch_add(1);
                std::cout << "job_2 shared (" << job_shared.load() << ")\n";
                return;
            } else {      //布尔标记为假,则修改'job_exclusive'
                ++job_exclusive;
                std::cout << "job_2 exclusive (" << job_exclusive << ")\n";
                std::this_thread::sleep_for(interval);
            }
        }
    }
    
    int main() 
    {
        std::thread thread_1(job_1);
        std::thread thread_2(job_2);
        thread_1.join();
        thread_2.join();
    
        getchar();
        return 0;
    }
    

    由示例程序可以看出,原子布尔类型可以实现互斥锁的部分功能,但在使用条件变量condition variable时,仍然需要mutex保护对condition variable的消费,即使condition variable是一个atomic object。

    2.4 使用原子类型实现自旋锁

    自旋锁(spinlock)与互斥锁(mutex)类似,在任一时刻最多只能有一个持有者,但如果资源已被占用,互斥锁会让资源申请者进入睡眠状态,而自旋锁不会引起调用者睡眠,会一直循环判断该锁是否成功获取。自旋锁是专为防止多处理器并发而引入的一种锁,它在内核中大量应用于中断处理等部分(对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,即在标志寄存器中关闭/打开中断标志位,不需要自旋锁)。

    对于多核处理器来说,检测到锁可用与设置锁状态两个动作需要实现为一个原子操作,如果分为两个原子操作,则可能一个线程在获得锁后设置锁前被其余线程抢到该锁,导致执行错误。这就需要原子库提供对原子变量“读-修改-写(Read-Modify-Write)”的原子操作,上文原子类型支持的操作中就提供了RMW(Read-Modify-Write)原子操作,比如a.exchange(val)与a.compare_exchange(expected,desired)。

    标准库还专门提供了一个原子布尔类型std::atomic_flag,不同于所有 std::atomic 的特化,它保证是免锁的,不提供load()与store(val)操作,但提供了test_and_set()与clear()操作,其中test_and_set()就是支持RMW的原子操作,可用std::atomic_flag实现自旋锁的功能,代码如下:

    //atomic2.cpp 使用原子布尔类型实现自旋锁的功能
    
    #include <thread>
    #include <vector>
    #include <iostream>
    #include <atomic>
     
    std::atomic_flag lock = ATOMIC_FLAG_INIT;       //初始化原子布尔类型
     
    void f(int n)
    {
        for (int cnt = 0; cnt < 100; ++cnt) {
            while (lock.test_and_set(std::memory_order_acquire))  // 获得锁
                 ; // 自旋
            std::cout << n << " thread Output: " << cnt << '\n';
            lock.clear(std::memory_order_release);               // 释放锁
        }
    }
     
    int main()
    {
        std::vector<std::thread> v;     //实例化一个元素类型为std::thread的向量
        for (int n = 0; n < 10; ++n) {
            v.emplace_back(f, n);       //以参数(f,n)为初值的元素放到向量末尾,相当于启动新线程f(n)
        }
        for (auto& t : v) {     //遍历向量v中的元素,基于范围的for循环,auto&自动推导变量类型并引用指针指向的内容
            t.join();           //阻塞主线程直至子线程执行完毕
        }
    
        getchar();
        return 0;
    }
    

    自旋锁除了使用atomic_flag的TAS(Test And Set)原子操作实现外,还可以使用普通的原子类型std::atomic实现:其中a.exchange(val)是支持TAS原子操作的,a.compare_exchange(expected,desired)是支持CAS(Compare And Swap)原子操作的,感兴趣可以自己实现出来。其中CAS原子操作是无锁编程的主要实现手段,我们接着往下介绍无锁编程。

    三、如何进行无锁编程

    3.1 什么是无锁编程

    在原子操作出现之前,对共享数据的读写可能得到不确定的结果,所以多线程并发编程时要对使用锁机制对共享数据的访问过程进行保护。但锁的申请释放增加了访问共享资源的消耗,且可能引起线程阻塞、锁竞争、死锁、优先级反转、难以调试等问题。

    现在有了原子操作的支持,对单个基础数据类型的读、写访问可以不用锁保护了,但对于复杂数据类型比如链表,有可能出现多个核心在链表同一位置同时增删节点的情况,这将会导致操作失败或错序。所以我们在对某节点操作前,需要先判断该节点的值是否跟预期的一致,如果一致则进行操作,不一致则更新期望值,这几步操作依然需要实现为一个RMW(Read-Modify-Write)原子操作,这就是前面提到的CAS(Compare And Swap)原子操作,它是无锁编程中最常用的操作。

    既然无锁编程是为了解决锁机制带来的一些问题而出现的,那么无锁编程就可以理解为不使用锁机制就可保证多线程间原子变量同步的编程。无锁(lock-free)的实现只是将多条指令合并成了一条指令形成一个逻辑完备的最小单元,通过兼容CPU指令执行逻辑形成的一种多线程编程模型。

    无锁编程是基于原子操作的,对基本原子类型的共享访问由load()与store(val)即可保证其并发同步,对抽象复杂类型的共享访问则需要更复杂的CAS来保证其并发同步,并发访问过程只是不使用锁机制了,但还是可以理解为有锁止行为的,其粒度很小,性能更高。对于某个无法实现为一个原子操作的并发访问过程还是需要借助锁机制来实现。

    3.1 CAS原子操作实现无锁编程

    CAS原子操作主要是通过函数a.compare_exchange(expected,desired)实现的,其语义为“我认为V的值应该为A,如果是,那么将V的值更新为B,否则不修改并告诉V的值实际为多少”,CAS算法的实现伪码如下:

    bool compare_exchange_strong(T& expected, T desired) 
    { 
        if( this->load() == expected ) { 
            this->store(desired); 
            return true; 
        } else {
            expected = this->load();
        	return false; 
        } 
    }
    

    下面尝试实现一个无锁栈,代码如下:

    //atomic3.cpp 使用CAS操作实现一个无锁栈
    
    #include <atomic>
    #include <iostream>
    
    template<typename T>
    class lock_free_stack
    {
    private:
        struct node
        {
            T data;
            node* next;
            node(const T& data) : data(data), next(nullptr) {}
        };
        std::atomic<node*> head;
    
     public:
        lock_free_stack(): head(nullptr) {}
        void push(const T& data)
        {
            node* new_node = new node(data);
            do{
                new_node->next = head.load();   //将 head 的当前值放入new_node->next
            }while(!head.compare_exchange_strong(new_node->next, new_node));
            // 如果新元素new_node的next和栈顶head一样,证明在你之前没人操作它,使用新元素替换栈顶退出即可;
            // 如果不一样,证明在你之前已经有人操作它,栈顶已发生改变,该函数会自动更新新元素的next值为改变后的栈顶;
            // 然后继续循环检测直到状态1成立退出;
        }
        T pop()
        {
            node* node;
            do{
                node = head.load();
            }while (node && !head.compare_exchange_strong(node, node->next));
    
            if(node) 
                return node->data;
        }
    };
     
    int main()
    {
        lock_free_stack<int> s;
        s.push(1);
        s.push(2);
        s.push(3);
        std::cout << s.pop() << std::endl;
        std::cout << s.pop() << std::endl;
        
        getchar();
        return 0;
    }
    

    程序注释中已经解释的很清楚了,在将数据压栈前,先通过比较原子类型head与新元素的next指向对象是否相等来判断head是否已被其他线程修改,根据判断结果选择是继续操作还是更新期望,而这一切都是在一个原子操作中完成的,保证了在不使用锁的情况下实现共享数据的并发同步。

    CAS 看起来很厉害,但也有缺点,最著名的就是 ABA 问题,假设一个变量 A ,修改为 B之后又修改为 A,CAS 的机制是无法察觉的,但实际上已经被修改过了。如果在基本类型上是没有问题的,但是如果是引用类型呢?这个对象中有多个变量,我怎么知道有没有被改过?聪明的你一定想到了,加个版本号啊。每次修改就检查版本号,如果版本号变了,说明改过,就算你还是 A,也不行。

    上面的例子节点指针也属于引用类型,自然也存在ABA问题,比如在线程2执行pop操作,将A,B都删掉,然后创建一个新元素push进去,因为操作系统的内存分配机制会重复使用之前释放的内存,恰好push进去的内存地址和A一样,我们记为A’,这时候切换到线程1,CAS操作检查到A没变化成功将B设为栈顶,但B是一个已经被释放的内存块。该问题的解决方案就是上面说的通过打标签标识A和A’为不同的指针,具体实现代码读者可以尝试实现。

    更多文章:

    展开全文
  • 主要介绍了Java多线程Atomic包操作原子变量与原子类详解,简单介绍了Atomic,同时涉及java.util.concurrent中的原子变量,Atomic类的作用等相关内容,具有一定参考价值,需要的朋友可以了解下。
  • 多线程程序中的原子操作
  • 项目中经常用遇到多线程操作共享数据问题,常用的处理方式是对共享数据进行加锁,如果多线程操作共享变量也同样采用这种方式。 为什么要对共享变量加锁或使用原子操作?如两个线程操作同一变量过程中,一个线程执行...
  • 在单线程的模式下,我们针对某个变量的修改是不会产生数据的脏读和脏写的,因为它只有一个操作来对变量进行读写操作,但是在多线程模式下就不一样了,如果多个线程对一个变量进行修改操作,那么到底哪一个线程修改的...
  • 原子操作就是在多线程程序中“最小的且不可并行化的”操作,意味着多个线程访问同一个资源时,有且仅有一个线程能对资源进行操作。通常情况下原子操作可以通过互斥的访问方式来保证,例如Linux下的互斥锁(mutex),...
  • Java多线程原子操作

    千次阅读 2018-03-09 21:59:48
    又如i++就不是一个原子操作,它相当于语句i=i+1;这里包括读取i,i+1,结果写入内存三个操作单元。因此如果操作不符合原子性操作,那么整个语句的执行就会出现混乱,导致出现错误的结果,从而导致线程安全问题。因此...
  • 经历了很奇奇怪怪的bug,整理一下。先描述要做的事情以及怎么做: ...也许有人会问,cuda提供了一些原子操作函数,能不能直接用呢?cuda提供的原子函数适用于简单的单一变量判断加减,而对于需要复
  • Java多线程原子

    千次阅读 2018-05-18 14:39:23
    友情提示:作为一个java小白最近在看java多线程知识,东西还是比较多,推荐大家去看《Java多线程编程指南》,怕自己忘了,所以决定码些字。 开始之前,建议大家一定要系统地学习一下操作系统,并且不能光看网上碎片...
  • c++基础封装(线程、锁、定时器、原子操作等),c++封装,接口方便好用。
  • 多线程三大特性——原子

    千次阅读 2022-01-10 20:33:40
    如果一个操作是原子性的,那么多线程并发的情况下,就不会出现变量被修改的情况。 比如 a=0;(a非long和double类型) 这个操作是不可分割的,那么说这个操作是原子操作。再比如:a++; 这个操作实际是a = a + 1;...
  • 原子操作(原子性) 所谓的原子操作是指不会被线程的调度机制所打断的操作;这种操作一旦开始就一直运行到结束,在执行一个线程的过程中不会切换到...在多线程中,不能被其他进程或线程打断的操作就叫原子操作 ...
  • C++多线程原子操作、线程阻塞

    千次阅读 2018-11-29 21:55:59
    原子操作 在C++11中,&lt;Thread&gt;头文件包含了Thread类,提供线程的管理。 原子操作:不可被中断的一个或者一系列操作,这些操作要一次性执行完毕,或者一个都不执行。 多线程存在的问题 在多线程中...
  • 什么是原子操作类AtomicInteger 我们知道java并发机制中主要有三个特性需要我们去考虑,原子性、可见性和有序性。synchronized关键字可以保证可见性和有序性却无法保证原子性。而这个AtomicInteger的作用就是为了...
  • 多线程计数器——原子操作

    千次阅读 2014-09-28 17:33:50
    众所周知,多线程下计数存在着计数不正确的问题。这个问题的根源在于多个线程对同一个变量可以同时访问(修改)。这样就造成了修改后的结果不一致。  首先在这里先强调一点,volatile 关键字并不能提供多线程安全...
  • SQLite多线程并发操作

    万次阅读 2018-11-27 15:11:07
    SQLite多线程并发操作 先理清楚一个概念 多线程操作和多数据库连接操作是不同的概念 多线程操作多线程操作数据库时可以使用一个数据库连接,也可以每个线程使用各自的数据库连接 多数据库连接操作:即每个...
  • 可以用原子操作变量来让变量能够在线程中使用 用atomic头文件引入原子操作 以下用atomic包裹变量让int变成原子操作 可以用aotmic操作变量进行如下操作从而保证变量的原子性: 但是当用两个变量两个原子...
  • c# 多线程学习笔记(三)原子操作

    千次阅读 2016-08-16 11:08:37
    以下多线程对int型变量x的操作,哪几个不需要进行同步 A. x=y; B. x++; C. ++x; D. x=1;   从上一节对“同步”概念的介绍得知,同步是指多个线程对资源的访问保证一定的顺序。 以上四个操作都是对资源x的访问...
  • C#中的原子操作

    千次阅读 2020-09-11 00:50:58
    所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何的线程切换,它不一定是一条指令,可以是条指令。 原子操作的原理 一般情况下,当我们对一个整型变量进行...
  • 多线程核心知识:原子

    千次阅读 2019-03-03 23:26:44
    原子操作定义:原子操作可以是一个步骤,也可以是个操作步骤,但是其顺序不可以被打乱,也不可以被切割而只执行其中的一部分(不可中断性)。将整个操作视为一个整体是原子性的核心特征。 存在竞争条件,线程不...
  • 如何保证多线程原子

    千次阅读 2019-08-22 16:57:02
    原子性 定义: 原子是世界上的最小单位,具有不可分割性。...非原子操作都会存在线程安全问题,需要我们使用相关技术(比如sychronized)让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原...
  • 多线程中的原子

    千次阅读 2018-03-06 16:08:13
    原子是世界上的最小单位,具有不可分割性。...非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。java的concurren...
  • 多线程-原子操作

    千次阅读 2014-12-10 23:07:53
    一、何谓Atomic?  Atomic一词跟原子...通常来说,原子指令由硬件提供,供软件来实现原子方法(某个线程进入该方法后,就不会被中断,直到其执行完成)    在x86 平台上,CPU提供了在指令执行期间对总线
  • 原子原子性:即一个操作或者操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
  • 多线程中操作全局变量一般都会引起线程冲突,为了解决线程冲突,引入原子操作。所谓原子操作,是指不会被线程调度机制打断的操作,操作一旦开始,就得执行到结束为止。原子操作可以是一个步骤,也可以是多个操作...
  • Java多线程编程基础三(原子性,可见性和有序性)

    万次阅读 多人点赞 2018-04-27 16:18:40
    在单线程环境下我们可以认为整个步骤都是原子操作,但是在多线程环境下则不同,Java只保证了基本数据类型的变量和赋值操作才是原子性的(注:在32位的JDK环境下,对64位数据的读取不是原子操作*,如long、double...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 223,327
精华内容 89,330
关键字:

多线程原子操作

友情链接: jdt.rar