C中的原子交换

cfh9epnr  于 2023-04-05  发布在  其他
关注(0)|答案(2)|浏览(146)

我想我忽略了一些明显的东西。我有这样的代码:

int *a = <some_address>;
int *b = <another_address>;

// ...

// desired atomic swap of addresses a and b here
int *tmp = a;
a = b; b = tmp;

我想以原子的方式来做这件事。我一直在研究__c11_atomic_exchange和OSX上的OSAtomic方法,但似乎没有一个方法能以原子的方式执行直接的交换。它们都能以原子的方式将一个值写入2个变量中的 1,但不能同时写入两个变量。
有什么想法吗

njthzxwz

njthzxwz1#

这取决于你的体系结构,什么是有效和高效的可能。在任何情况下,你都需要你想要交换的两个指针在内存中是相邻的,最简单的是你有他们一些像

typedef struct pair { void *a[2]; } pair;

然后,如果你有一个完整的C11编译器,你可以使用_Atomic(pair)作为一个原子类型来交换这样一个pair的内容:首先将实际对加载到临时中,用交换的两个值构造一个新的对,进行比较交换以存储新值,并在必要时进行迭代:

inline
void pair_swap(_Atomic(pair) *myPair) {
  pair actual = { 0 };
  pair future = { 0 };

  while (!atomic_compare_exchange_weak(myPair, &actual, future)) {
      future.a[0] = actual.a[1];
      future.a[1] = actual.a[0];
  }
}

根据你的体系结构,这可能是实现一个 * 锁自由 * 原语操作或没有。现代64位体系结构通常有128位原子,这可能会导致一些128位汇编指令。
但这在很大程度上取决于您的平台上原子操作的实现质量。P99将为您提供一个实现原子操作的“类函数”部分的实现,并且在检测到时支持128位交换;在我的机器(64位Intel Linux)上,这有效地编译成lock cmpxchg16b指令。
相关文件:

  1. https://en.cppreference.com/w/c/language/atomic
  2. https://en.cppreference.com/w/c/thread
  3. https://en.cppreference.com/w/c/atomic/atomic_exchange
  4. https://en.cppreference.com/w/c/atomic/atomic_compare_exchange
lo8azlld

lo8azlld2#

快速总结:基于自旋锁原子交换应答

这个代码片段就是我们将要构建的内容。
对于多核系统上的基本自旋锁(高速,无上下文切换互斥)方法,这是自旋锁最好的地方,您可以像这样对变量值(在这种情况下是指针)进行原子交换:

int *a = &some_int1;  // ptr a
int *b = &some_int2;  // ptr b
spinlock_t spinlock = SPINLOCK_UNLOCKED;

// swap pointers `a` and `b` (note: swapping the pointers themselves, not the
// integer values they point to)
atomic_swap_int_ptr(&spinlock, &a, &b);

详细信息如下:

C11中通用的自旋锁方法(用于 Package any 需要原子化的“critical section”)

如果您的架构支持使用in the main answer here提供的数组“pair”进行无锁原子交换,那么请考虑一下。它可能是无锁的,因此比使用互斥锁或自旋锁更有效。
我想更深入地研究这个问题,并尝试使用自旋锁。我已经 not speed分析了各种技术(数组“对”,互斥和自旋锁),但如果我这样做,看看它们在速度上的比较会非常有趣和学术。
不管怎样,就像我说的:你也可以使用互斥锁。
或者,如果你不想让线程在互斥体不能立即可用时进入睡眠状态,你可以通过使用旋转锁来提高效率,因为旋转锁必须旋转的次数通常很小,所以旋转锁比让线程进入睡眠状态并进行整个上下文切换要快。
C11有一整套的原子类型和函数,可以让你轻松地实现自己的自旋锁来实现这一点。
但是,请记住,基本的自旋锁不能在裸机(没有操作系统)单核(处理器)系统上使用,因为如果主上下文获取锁,然后当锁仍然被获取时,ISR触发并试图获取锁,这将导致死锁。ISR将无限期阻塞。
为了避免这种情况,必须使用更复杂的锁定机制,例如具有超时的自旋锁,如果锁被占用,则自动重新调度自己稍晚的时间的ISR,自动让步等。

因此,最好为具有操作系统和调度程序的多核系统保留旋转锁。

C11中_Atomic类型的基本自旋锁实现

#include <stdbool.h>
#include <stdatomic.h>

#define SPINLOCK_UNLOCKED 0
#define SPINLOCK_LOCKED   1

typedef volatile atomic_bool spinlock_t;

// This is how you'd create a new spinlock to use in the functions below.
// spinlock_t spinlock = SPINLOCK_UNLOCKED;

/// \brief  Set the spin lock to `SPINLOCK_LOCKED`, and return the previous 
///  state of the `spinlock` object.
/// \details    If the previous state was `SPINLOCK_LOCKED`, you can know that
///  the lock was already taken, and you do NOT now own the lock. If the
///  previous state was `SPINLOCK_UNLOCKED`, however, then you have now locked
///  the lock and own the lock.
bool atomic_test_and_set(spinlock_t* spinlock)
{
    bool spinlock_val_old = atomic_exchange(spinlock, SPINLOCK_LOCKED);
    return spinlock_val_old;
}

/// Spin (loop) until you take the spin lock.
void spinlock_lock(spinlock_t* spinlock)
{
    // Spin lock: loop forever until we get the lock; we know the lock was
    // successfully obtained after exiting this while loop because the
    // atomic_test_and_set() function locks the lock and returns the previous
    // lock value. If the previous lock value was SPINLOCK_LOCKED then the lock
    // was **already** locked by another thread or process. Once the previous
    // lock value was SPINLOCK_UNLOCKED, however, then it indicates the lock
    // was **not** locked before we locked it, but now it **is** locked because
    // we locked it, indicating we own the lock.
    while (atomic_test_and_set(spinlock) == SPINLOCK_LOCKED) {};
}

/// Release the spin lock.
void spinlock_unlock(spinlock_t* spinlock)
{
    *spinlock = SPINLOCK_UNLOCKED;
}

现在,您可以在您的多核系统中使用自旋锁,如下所示:

/// Atomically swap two pointers to int (`int *`). 
/// NB: this does NOT swap their contents, meaning: the integers they point to.
/// Rather, it swaps the pointers (addresses) themselves.
void atomic_swap_int_ptr(spinlock_t * spinlock, int ** ptr1, int ** ptr2)
{
    spinlock_lock(spinlock)
    // critical, atomic section start

    int * temp = *ptr1;
    *ptr1 = *ptr2;
    *ptr2 = temp;
    
    // critical, atomic section end
    spinlock_unlock(spinlock)
}

// Demo to perform an address swap

int *a = &some_int1;
int *b = &some_int2;
spinlock_t spinlock = SPINLOCK_UNLOCKED;

// swap pointers `a` and `b` (note: swapping the pointers themselves, not the
// integer values they point to)
atomic_swap_int_ptr(&spinlock, &a, &b);  // <=========== final answer

参考文献

  1. https://en.wikipedia.org/wiki/Test-and-set#Pseudo-C_implementation_of_a_spin_lock
    1.*****C11并发支持库及atomic_bool等类型:https://en.cppreference.com/w/c/thread
  2. C11 atomic_exchange()功能:https://en.cppreference.com/w/c/atomic/atomic_exchange

其他链接

1.请注意,您也可以使用C11的struct atomic_flag及其相应的atomic_flag_test_and_set()函数,而不是像我上面所做的那样使用atomic_exchange()创建自己的atomic_test_and_set()函数。

  1. C11中的_Atomic类型:https://en.cppreference.com/w/c/language/atomic
  2. C++11 Implementation of Spinlock using header <atomic>

待办事项

1.这个实现需要修改 * 添加安全防死锁机制,如自动延迟,超时和重试 *,以防止 * 单核和裸机 * 系统上的死锁。请参阅我的注解:Add basic mutex (lock) and spin lock implementations in C11, C++11, AVR, and STM32和这里:What are the various ways to disable and re-enable interrupts in STM32 microcontrollers in order to implement atomic access guards?

相关问题