基于 futex 的 4 字节单写入器/多读取器锁

发布于 2024-09-28 16:04:22 字数 280 浏览 8 评论 0原文

寻找一个基于 futex 的单写入器/多读取器锁的最小实现,不需要超出单个 4 字节 futex 状态变量的空间开销。

一些背景:我有一个应用程序,它将在数千万到数亿个小对象中的每一个中嵌入一个锁。由于锁定的粒度非常细以及应用程序的结构,我预计争用最少。此外,作家将变得稀少,而有竞争力的作家则更加稀少。出于所有这些原因,在这种特定设置中,(理论上)容易出现“听到雷声”现象的解决方案是完全可以接受的。

Looking for a minimal, futex-based implementation of a single-writer/multiple-readers lock requiring no space overhead beyond a single 4-byte futex state variable.

Some background: I have an application which will embed a lock within each of tens to hundreds of millions of small objects. Because of the very fine grained nature of the locking and the structure of the application I anticipate minimal contention. Further, writers will be rare and contending writers rarer still. For all of these reasons, in this particular setting, a solution prone (in theory) to the "thundering heard" phenomenon is quite acceptable.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

时光磨忆 2024-10-05 16:04:22

您可以在 https://gist.github.com/smokku/653c469d695d60be4fe8170630ba8205 找到我的

实现是只能有一个线程获取写入锁(futex value 0),锁可以打开(futex value 1),或者可以有多个读取线程( futex 值大于 1)。因此,低于 1 的值(只有一个)会阻止 futex 上的读取器和写入器,而高于 1 的值仅阻止写入器。解锁线程会唤醒等待线程之一,但您需要小心,不要消耗仅由写入线程唤醒的读取线程。

#define cpu_relax() __builtin_ia32_pause()
#define cmpxchg(P, O, N) __sync_val_compare_and_swap((P), (O), (N))

static unsigned _lock = 1; // read-write lock futex
const static unsigned _lock_open = 1;
const static unsigned _lock_wlocked = 0;

static void _unlock()
{
    unsigned current, wanted;
    do {
        current = _lock;
        if (current == _lock_open) return;
        if (current == _lock_wlocked) {
            wanted = _lock_open;
        } else {
            wanted = current - 1;
        }
    } while (cmpxchg(&_lock, current, wanted) != current);
    syscall(SYS_futex, &_lock, FUTEX_WAKE_PRIVATE, 1, NULL, NULL, 0);
}

static void _rlock()
{
    unsigned current;
    while ((current = _lock) == _lock_wlocked || cmpxchg(&_lock, current, current + 1) != current) {
        while (syscall(SYS_futex, &_lock, FUTEX_WAIT_PRIVATE, current, NULL, NULL, 0) != 0) {
            cpu_relax();
            if (_lock >= _lock_open) break;
        }
        // will be able to acquire rlock no matter what unlock woke us
    }
}

static void _wlock()
{
    unsigned current;
    while ((current = cmpxchg(&_lock, _lock_open, _lock_wlocked)) != _lock_open) {
        while (syscall(SYS_futex, &_lock, FUTEX_WAIT_PRIVATE, current, NULL, NULL, 0) != 0) {
            cpu_relax();
            if (_lock == _lock_open) break;
        }
        if (_lock != _lock_open) {
            // in rlock - won't be able to acquire lock - wake someone else
            syscall(SYS_futex, &_lock, FUTEX_WAKE_PRIVATE, 1, NULL, NULL, 0);
        }
    }
}

You will find my implementation at https://gist.github.com/smokku/653c469d695d60be4fe8170630ba8205

The idea is that there can be only one thread taking the lock for write (futex value 0), lock can be open (futex value 1) or there can be many reading threads (futex values greater than 1). So values below 1 (there is only one) block both readers and writers on futex, and values above 1 block only writers. Unlocking thread wakes one of waiting threads, but you need to be careful not to consume a readers only wake by a writer thread.

#define cpu_relax() __builtin_ia32_pause()
#define cmpxchg(P, O, N) __sync_val_compare_and_swap((P), (O), (N))

static unsigned _lock = 1; // read-write lock futex
const static unsigned _lock_open = 1;
const static unsigned _lock_wlocked = 0;

static void _unlock()
{
    unsigned current, wanted;
    do {
        current = _lock;
        if (current == _lock_open) return;
        if (current == _lock_wlocked) {
            wanted = _lock_open;
        } else {
            wanted = current - 1;
        }
    } while (cmpxchg(&_lock, current, wanted) != current);
    syscall(SYS_futex, &_lock, FUTEX_WAKE_PRIVATE, 1, NULL, NULL, 0);
}

static void _rlock()
{
    unsigned current;
    while ((current = _lock) == _lock_wlocked || cmpxchg(&_lock, current, current + 1) != current) {
        while (syscall(SYS_futex, &_lock, FUTEX_WAIT_PRIVATE, current, NULL, NULL, 0) != 0) {
            cpu_relax();
            if (_lock >= _lock_open) break;
        }
        // will be able to acquire rlock no matter what unlock woke us
    }
}

static void _wlock()
{
    unsigned current;
    while ((current = cmpxchg(&_lock, _lock_open, _lock_wlocked)) != _lock_open) {
        while (syscall(SYS_futex, &_lock, FUTEX_WAIT_PRIVATE, current, NULL, NULL, 0) != 0) {
            cpu_relax();
            if (_lock == _lock_open) break;
        }
        if (_lock != _lock_open) {
            // in rlock - won't be able to acquire lock - wake someone else
            syscall(SYS_futex, &_lock, FUTEX_WAKE_PRIVATE, 1, NULL, NULL, 0);
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文