std :: atomic如何确保原子性

时间:2019-01-20 18:14:15

标签: c++ atomic

如果我有代码import { Injectable } from '@angular/core'; import { Headers, Http, Response } from '@angular/http'; import 'rxjs/Rx'; import { Observable } from 'rxjs/Observable'; @Injectable() export class ServerService { constructor(private http: Http) {} storeServers(servers: any[]) { const headers = new Headers({'Content-Type': 'application/json'}); // return this.http.post('https://udemy-ng-http.firebaseio.com/data.json', // servers, // {headers: headers}); return this.http.put('https://udemy-ng-http.firebaseio.com/data.json', servers, {headers: headers}); } getMegyek() { return this.http.get('http://localhost/Varosok/Controller/Controller/ControllerCity.php?content=megyek') .map( (response: Response) => { const data = response.json(); /*for (const server of data) { server.name = 'FETCHED_' + server.name; }*/ return data; } ) .catch( (error: Response) => { return Observable.throw('Something went wrong'); } ); } getAppName() { return this.http.get('https://udemy-ng-http.firebaseio.com/appName.json') .map( (response: Response) => { return response.json(); } ); } } ,现在我知道执行此操作需要多个CPU级别的操作,但是将a = a + 1定义为a怎么使这些多个事务原子化?

是否更改了CPU指令的执行方式。我以为它将以某种方式将指令数减少到1,这样任何上下文切换都不会导致不可靠的结果,但是它是如何做到的呢?

如果编译器总是可以创建这样的代码,为什么不总是这样做呢?

2 个答案:

答案 0 :(得分:6)

如果有一个原子指令可以发布(用于已知的可能的原子操作),那么该原子指令将被发布,否则它将具有锁定机制。

有一个函数(C ++ 17)告诉您原子类型是否始终是无锁的:is_always_lock_free。 请注意,如果此函数返回false,则至少某些操作不是非锁定的(不一定全部锁定)。这些非无锁操作通常会比原子操作贵(它们本身比传统操作贵)。

并非所有硬件都支持原子操作的所有组合,因此不同的编译器后端将生成不同的解决方案,有时使用单个原子操作,有时使用锁定机制。

因此它不能总是创建这样的1指令代码。

答案 1 :(得分:3)

  

[B] ut如何将std :: atomic定义为多个   交易是原子的吗?

它不会使“多个交易”成为任意表达式中的原子(例如,在您的a = a + 1示例中无济于事)。相反,您需要使用像a++这样的操作,它必须保证是原子的。在这种情况下,其实现方式取决于编译器和硬件,但是最常见的策略是:

  • 使用一条指令 atomic操作,它可以自动增加内存中的值。在x86上,这将是something like lock add指令。
  • 某些类型的compare-and-exchange (CAS)load-linked store-conditional (LL-SC)循环用于重复尝试以原子方式进行增量。您可以在MIPS64上see LL-SC in action
  • 最后,在不支持此类操作的平台上,或者在数据类型与那些指令不兼容的平台上,可以在执行常规非原子指令的操作时获得锁以排除任何并发访问。大多数主流平台都不需要依赖于原子类型,但是您仍然可以在旧的ARM编译器上看到an example here

通过检查生成的程序集,您可以检查编译器和硬件组合上的行为。有时这很棘手,因为编译器可能会调用运行时库中实现的函数,在这种情况下,您必须检查该函数的源代码或反汇编代码。这意味着,如果运行时库的实现不同,则同一二进制文件可以在不同的主机上具有不同的原子操作实现!

  

如果编译器总是可以创建这样的代码,为什么不总是这样做   那?

编译器并不总是生成这些,因为它们在硬件级别上很昂贵。例如,在大多数现代CPUs 1 中,正常(非原子)添加通常需要1个周期或更短的 2 ,而原子添加可能需要15至100个周期。通常,使用CAS或LL-SC的方法速度甚至更慢,并且需要重试循环,从而增大了二进制文件的大小。


1 在某些微控制器类CPU上可能只有几个周期-但是原子操作通常不太重要,因为可能没有多个内核。

2 这取决于您的测量方式-加法通常需要一个周期完成 complete (等待时间),但是您通常可以在同一时间执行多个独立的加法周期。例如,现代的Intel CPU可以在一个周期内执行四个。