这让我困惑了很长时间。
给定基本的原子原语(例如比较和交换),我可以了解如何实现自旋锁(我可以从中构建互斥体)。
但是,我不知道如何从中构建条件变量。这是怎么做到的?
比较和交换函数可以用于原子交换变量吗?我在x86_64 RedHat Linux上通过gcc使用C/C++,特别是__sync builtins.例:
int x = 0, y = 1;
y = __sync_val_compare_and_swap(&x, x, y);
Run Code Online (Sandbox Code Playgroud)
我认为这归结为x是否可以在&x和x之间变化; 例如,如果&x构成一个操作,则x可能会在参数中的&x和x之间进行更改.我想假设上面隐含的比较总是正确的; 我的问题是我是否可以.显然有CAS的bool版本,但是我不能让旧的x写入y.
一个更有用的例子可能是从链表的头部插入或删除(gcc声称支持指针类型,所以假设是elem和head是):
elem->next = __sync_val_compare_and_swap(&head, head, elem); //always inserts?
elem = __sync_val_compare_and_swap(&head, head, elem->next); //always removes?
Run Code Online (Sandbox Code Playgroud)
我想创建一个非阻塞队列包描述使用算法通过马吉德·M. Michael和迈克尔·L.·斯科特并发应用在这里.
这需要使用"sync/atomic"包提供的原子CompareAndSwap .
但是我不确定以下伪代码的Go等价物是什么:
E9: if CAS(&tail.ptr->next, next, <node, next.count+1>)
Run Code Online (Sandbox Code Playgroud)
其中tail和next类型:
type pointer_t struct {
ptr *node_t
count uint
}
Run Code Online (Sandbox Code Playgroud)
并且node是类型:
type node_t struct {
value interface{}
next pointer_t
}
Run Code Online (Sandbox Code Playgroud)
如果我理解正确,似乎我需要用一个结构(一个指针和一个uint)来做一个CAS .使用atomic-package 可以实现这一点吗?
感谢帮助!
我想使用原子比较和交换,但不是等于,我想只在内存位置不等于旧值时进行交换.在C中有可能吗?
程序在C中使用std = c99,这是在64位机器上.
struct epochs {
volatile unsigned int epoch : 1;
volatile unsigned int pulse : 1;
volatile unsigned int active0 : 7;
volatile unsigned int active1 : 7;
volatile unsigned int counter0 : 24;
volatile unsigned int counter1 : 24;
};
Run Code Online (Sandbox Code Playgroud)
当我检查sizeof(epochs)它给了我12.
我可以告诉gcc不要通过添加__attribute((packed))来填充它; 所以我可以解决它.但是,我真的想知道为什么要添加4个字节来填充这个64位结构?
这里的主要内容是这个结构需要64位,因为它在64位原子交换操作中一次更新,当然这对12字节值不起作用.
我正在研究Java书籍中的并发包。我不太了解本书中有关CAS操作的内容。下面的代码示例是counter本书中的线程安全类。
public class Counter{
private AtomicInteger count = new AtomicInteger();
public void increment(){
count.getAndIncrement(); //atomic operation
}
....
}
Run Code Online (Sandbox Code Playgroud)
这是书中所说的。
实际上,即使诸如这样的方法也要
getAndIncrement()花费几个步骤来执行。此实现现在是线程安全的,因此称为CAS。CAS代表“比较并交换”。大多数现代CPU都有一组CAS指令。现在正在发生的事情的基本概述如下:
- 存储在count中的值将被复制到一个临时变量中。
- 临时变量增加。
- 将当前计数的值与原始值进行比较。如果未更改,则将旧值替换为新值。
好吧,我明白它关于多个步骤的说法。我不太了解的是枚举步骤中正在发生的事情。
- 存储在count中的值将被复制到一个临时变量中。
该临时变量在哪里?它在主存储器中,注册吗?还是特定于CPU体系结构?
- 将当前计数的值与原始值进行比较。如果未更改,则将旧值替换为新值。
原始值存储在哪里?不能是临时变量。那个人正在被修改,对吧?我想念什么?
谢谢
在优化程序时,我遇到了一些奇怪的性能结果,这些结果显示在以下BenchmarkDotNet基准测试中:
string _s, _y = "yo";
[Benchmark]
public void Exchange() => Interlocked.Exchange(ref _s, null);
[Benchmark]
public void CompareExchange() => Interlocked.CompareExchange(ref _s, _y, null);
Run Code Online (Sandbox Code Playgroud)
结果如下:
BenchmarkDotNet=v0.10.10, OS=Windows 10 Redstone 3 [1709, Fall Creators Update] (10.0.16299.192)
Processor=Intel Core i7-6700HQ CPU 2.60GHz (Skylake), ProcessorCount=8
Frequency=2531248 Hz, Resolution=395.0620 ns, Timer=TSC
.NET Core SDK=2.1.4
[Host] : .NET Core 2.0.5 (Framework 4.6.26020.03), 64bit RyuJIT
DefaultJob : .NET Core 2.0.5 (Framework 4.6.26020.03), 64bit RyuJIT
Method | Mean | Error | StdDev |
---------------- |----------:|----------:|----------:|
Exchange | 20.525 …Run Code Online (Sandbox Code Playgroud) 在下一种情况下,我将a atomic<uint64_t>用作计数器,并从5个或更多线程中对其进行递增,并在递增之前使用该值来做出一些决定。
atomic<uint64_t> global_counter;
void thread_funtion(){
uint64_t local_counter = global_counter.fetch_add(1,std::memory_order_relaxed);
if(local_counter == 24)
do_somthing(local_counter);
}
Run Code Online (Sandbox Code Playgroud)
thread_funtion()将由5个不同的线程执行。一旦我知道了,local_counter我的代码就不再关心global_counter在thread_funtion()运行时是否再次进行更改(业务逻辑的方式是每次thread_function()调用仅需要一个唯一的递增值)。
是std::memory_order_relaxed安全的,在这种情况下使用?
我假设简单的自旋锁不会进入操作系统等待这个问题的目的。
我发现简单的自旋锁通常使用lock xchgorlock bts代替 来实现lock cmpxchg。
cmpxchg但是如果期望不匹配,是否会避免写入该值?那么失败的尝试不是更便宜吗cmpxchg?
或者即使cmpxchg发生故障也会写入数据并使其他核心的缓存线无效?
这个问题类似于什么具体将 x86 缓存行标记为脏 - 任何写入,还是需要显式更改?,但它是特定的cmpxchg,而不是普遍的。
我正在尝试标记和取消标记指针,以便我可以实现非阻塞链表。我检查过在我的体系结构上从未使用过最后一位,因此我尝试使用更改它来标记/取消标记指针。
我正在尝试执行 OR 将最后一位设置为 1,执行 AND 来取消设置它,执行 AND 来检查它是否设置为 1。问题是,当我对指针执行按位(注释的宏)操作时,我无法取消引用它。即使指针的整数值是正确的,取消引用它也会导致分段错误。
更具体地说,这#define unmark(x) (x & (uintptr_t) 0xfffffffe)就是导致分段错误的原因。如果我不使用它(而是使用它#define unmark(x) x - 1),程序就可以工作。
递增和递减指针似乎有效,但它可能会使解决方案架构变得特定。这是因为在我的架构上,指针总是以 8 结尾,最后一位设置为 0。如果不是这种情况,我的解决方案将不是很可移植。
我知道操作指针可能无论如何都不可移植,但这是该算法所必需的。如果有人知道导致问题的原因,那就太棒了。
这是我用来测试解决方案的代码:
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
//This produces segfault, for some reason
//#define unmark(x) (x & (uintptr_t) 0xfffffffe)
//#define mark(x) (x | (uintptr_t) 0x00000001)
#define is_marked(x) ((long) x & 0x00000001)
#define mark(x) x + 1
#define unmark(x) x - 1
struct Example {
long x;
long y;
};
int main() …Run Code Online (Sandbox Code Playgroud)