多线程原子操作方法(atomic,compare_and_swap)

2024-04-12 02:32

本文主要是介绍多线程原子操作方法(atomic,compare_and_swap),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在前面的多处理器编程艺术文章中,谈到了lock free算法。多线程编程通常碰到的问题是很多操作的非原子性(non-atomic)。
本文通过程序实例,说明多线程中由于访问同一数据变量可能造成写丢失的情况。并通过mutex、atomic、compare_and_swap逻辑完成对于多线程的控制。
1.非线程安全方式
多个线程并发地将一个变量相加n次。

volatile unsigned long counter = 0;void
Add(void *arg)
{int i;int iter = * ((int*)arg);for (i = 0; i < iter; i++){++counter;   //非线程安全的}return NULL;
}int
main(int argc, char *argv[]) 
{int i, num, count;pthread_t *t;if (argc != 3){fprintf(stderr, "usage %s: <nthreads> <iterations>\n", argv[0]);return 1;}num = atoi(argv[1]);count = atoi(argv[2]);/* create threads */t = (pthread_t *) malloc(num * sizeof(pthread_t));for (i = 0; i < num; i++){assert(!pthread_create(&t[i], NULL, Add, (void *) &count));}for (i = 0; i < num; i++){assert(!pthread_join(t[i], NULL));}printf("all thread done, the result is:%lu\n", counter);}

其中,++counter语句为非线程安全的。我们通过反汇编查看具体的操作步骤:(gdb -q ./unsafe_add ,输入dsias Add查看结果)

8048540 <Add+16>:	mov    0x8049988,%eax
0x08048545 <Add+21>:	add    $0x1,%edx
0x08048548 <Add+24>:	add    $0x1,%eax
0x0804854b <Add+27>:	cmp    %ecx,%edx
0x0804854d <Add+29>:	mov    %eax,0x8049988


其中可以看出++counter实际上按照三条语句执行。
a.先将内存中的变量保存在eax寄存器中;
b.将寄存器内容+1;
c.将寄存器中的内容写回内存;
多线程运行时,可能出现交差的情况,如下所示:
MOV counter,%rax thread-0
MOV counter,%rax thread-1
ADD 0x01,%rax thread-0
ADD 0x01,%rax thread-1
MOV %rax,counter thread-0
MOV %rax,counter thread-1
此时多个线程执行,会将部分的执行结果覆盖,所以,创建5个线程,各增加100000次,可能出现的最后结果为:
all thread done, the result is:268565
2.互斥锁方式
通过互斥访问的方法,在同一时刻只能有一个线程进入临界区进行操作。
采用pthread库,使用mutex对象控制各线程对于counter变量的访问。

void
Add(void *arg)
{int i;int iter = * ((int*)arg);for (i = 0; i < iter; i++){pthread_mutex_lock(&mutex);++counter;  //critical sectionpthread_mutex_unlock(&mutex);}return NULL;
}

同一时刻,只能由一个线程改变counter的值。
注:此时counter要声明为volatile,不然可能会被有的编译器"虐".可以参加该链接。
http://concurrencykit.org/doc/concurrencyPrimitives.html#3.1
3. 原子操作方式
现代的CPU支持原子操作的指令。比如:xadd,配合lock指令,完成fetch_and_add操作。可以参见:
http://lists.cs.uiuc.edu/pipermail/llvmdev/2004-December/002793.html
http://en.wikipedia.org/wiki/Fetch-and-add#x86_implementation
我们通过gcc内联汇编的方式实现原子增加方法:

static inline unsigned int AtomicAdd(volatile unsigned long *mem, unsigned long add)
{unsigned long __tmp = add;__asm__ __volatile__("lock xaddq %0,%1":"+r" (add),"+m" (*mem): : "memory");return add + __tmp;
}

4. GCC Atomic Builtins方式
gcc从4.1版本开始提供了原子操作。可以参见:
http://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html
我们采用__sync_fetch_and_add方式。

void
Add(void *arg)
{int i;int iter = * ((int*)arg);for (i = 0; i < iter; i++){__sync_fetch_and_add(&counter, 1);}return NULL;
}

5.lock free方式
现代cpu主要通过cmpxchg指令来达到lockfree的效果,也就是compare_and_swap机制。可以参见:
http://en.wikipedia.org/wiki/Non-blocking_algorithm
http://en.wikipedia.org/wiki/Compare-and-swap
compare_and_swap机制逻辑如下:

/* atomic */
bool compare_and_swap (unsigned long *mem, unsigned long old, unsigned long new)
{if (*mem == old) {*mem = newreturn true;}return false;
}

比较内存mem中的值是否与给定的old值一致时,用new值替换mem中的数据。考虑++counter语句,线程将counter进行拷贝,增加后再更新counter,如果通过cas比较counter的值与拷贝的值不能,则说明有其他线程已经更新了counter,需要重新load,否则更新counter。

static inline char CAS(volatile unsigned long *mem, unsigned long old, unsigned long new)
{unsigned long r;__asm__ __volatile__("lock cmpxchg %k2, %1": "=a" (r), "+m" (*mem): "r" (new), "0" (old): "memory");return r == old ? 1 : 0;
}

6.测试
编写如下脚本,对于不同的方式执行时间进行测试:

for pg in atomic_add gcc_add mutex_add cas_add; do
#for pg in  mutex_add; dofor (( i=1; $i <= 10; ++i)); dolet nops=1000000/$iecho -n "$i $nops"time  ./$pg $i $nops 1> /dev/nulldone
done

可以看出mutex方式由于涉及到线程的切换,所花费的时间代价很大。

相关的代码为:
lockfree.tar
注:其中的xadd、cmpxchg指令是在64位主机下使用的,如果在32位主机下,可能需要使用xaddl、cmpxchgl指令。

http://www.financecomputing.net/wordpress/?p=245

这篇关于多线程原子操作方法(atomic,compare_and_swap)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/895861

相关文章

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

多线程解析报表

假如有这样一个需求,当我们需要解析一个Excel里多个sheet的数据时,可以考虑使用多线程,每个线程解析一个sheet里的数据,等到所有的sheet都解析完之后,程序需要提示解析完成。 Way1 join import java.time.LocalTime;public class Main {public static void main(String[] args) thro

Java 多线程概述

多线程技术概述   1.线程与进程 进程:内存中运行的应用程序,每个进程都拥有一个独立的内存空间。线程:是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换、并发执行,一个进程最少有一个线程,线程实际数是在进程基础之上的进一步划分,一个进程启动之后,进程之中的若干执行路径又可以划分成若干个线程 2.线程的调度 分时调度:所有线程轮流使用CPU的使用权,平均分配时间抢占式调度

Java 多线程的基本方式

Java 多线程的基本方式 基础实现两种方式: 通过实现Callable 接口方式(可得到返回值):

Lua 脚本在 Redis 中执行时的原子性以及与redis的事务的区别

在 Redis 中,Lua 脚本具有原子性是因为 Redis 保证在执行脚本时,脚本中的所有操作都会被当作一个不可分割的整体。具体来说,Redis 使用单线程的执行模型来处理命令,因此当 Lua 脚本在 Redis 中执行时,不会有其他命令打断脚本的执行过程。脚本中的所有操作都将连续执行,直到脚本执行完成后,Redis 才会继续处理其他客户端的请求。 Lua 脚本在 Redis 中原子性的原因

JAVA- 多线程

一,多线程的概念 1.并行与并发 并行:多个任务在同一时刻在cpu 上同时执行并发:多个任务在同一时刻在cpu 上交替执行 2.进程与线程 进程:就是操作系统中正在运行的一个应用程序。所以进程也就是“正在进行的程序”。(Windows系统中,我们可以在任务管理器中看 到进程) 线程:是程序运行的基本执行单元。当操作系统执行一个程序时, 会在系统中建立一个进程,该进程必须至少建立一个线

多线程篇(阻塞队列- LinkedBlockingDeque)(持续更新迭代)

目录 一、LinkedBlockingDeque是什么 二、核心属性详解 三、核心方法详解 addFirst(E e) offerFirst(E e) putFirst(E e) removeFirst() pollFirst() takeFirst() 其他 四、总结 一、LinkedBlockingDeque是什么 首先queue是一种数据结构,一个集合中

多线程篇(阻塞队列- LinkedBlockingQueue)(持续更新迭代)

目录 一、基本概要 1. 构造函数 2. 内部成员 二、非阻塞式添加元素:add、offer方法原理 offer的实现 enqueue入队操作 signalNotEmpty唤醒 删除线程(如消费者线程) 为什么要判断if (c == 0)时才去唤醒消费线程呢? 三、阻塞式添加元素:put 方法原理 图解:put线程的阻塞过程 四、非阻塞式移除:poll方法原理 dequ

spring笔记 多线程的支持

spring的工作机制 136  属性编辑器 140 spring事件的体系结构 168 Bean间的关系 109 继承 依赖 引用     Bean的继承          1 为了简化初始化的属性注入;          2 子Bean和父Bean相同的属性值,使用子Bean的     Bean的依赖 Srping控制相互依赖的Bean之间,属性注入的顺序,防止出错  depend-on

【编程底层思考】详解Java的JUC多线程并发编程底层组件AQS的作用及原理

Java中的AbstractQueuedSynchronizer(简称AQS)是位于java.util.concurrent.locks包中的一个核心组件,用于构建锁和其他同步器。AQS为实现依赖于FIFO(先进先出)等待队列的阻塞锁和相关同步器提供了一套高效、可扩展的框架。 一、AQS的作用 统一同步状态管理:AQS提供了一个int类型的成员变量state,用于表示同步状态。子类可以根据自己