自旋锁(C++实现)
1 简介
自旋锁是一种典型的无锁算法,在任何时刻,它都最多只能有一个保持者。当有一个线程试图获得一个已经被占用的锁时,该线程就会一直进行循环等待,直到该锁被释放。自旋锁的优点是不会使线程状态发生切换,一直处于用户态,即线程一直都是active的;缺点是如果一直获取不到锁,那线程就一直处于循环等待中,白白浪费CPU资源。自旋锁在某些场景下非常有效,尤其是临界区较小且持有时间短的情况下。然而,在高竞争或临界区较长的情况下,使用自旋锁可能导致性能下降,因此需谨慎选择。
2 不同自旋锁的实现
自旋锁的实现有多种方式,包括:
- 比较并交换(CAS):CAS是一种原子操作,它可以比较一个内存位置的值与一个期望值是否相等,如果相等则将该位置的值更新为一个新值。
- 测试并设置(TAS):TAS是一种原子操作,它可以测试一个内存位置的值是否为0,如果为0则将该位置的值设置为1。
- 互斥量:互斥量是一种同步原语,它可以确保同一时刻只有一个线程可以访问共享资源。
- 信号量:信号量是一种同步原语,它可以确保同一时刻只有一定数量的线程可以访问共享资源。
- 屏障:屏障是一种同步原语,它可以确保同一时刻只有一定数量的线程可以访问共享资源。
2.1 TASLock
TASLock的实现比较简单,只需要一个bool类型的变量flag来表示锁的状态,当flag为false时表示锁可用,当flag为true时表示锁不可用。当一个线程试图获取锁时,它会不断地检查flag的值,如果flag为false,则将flag设置为true,表示锁被占用,否则自旋等待。当一个线程释放锁时,它会将flag设置为false,表示锁可用。TASLock的实现如下所示:
class TASLock {
public:TASLock() : flag(false) {}void lock() {// 自旋等待直到能够获取锁while (flag.exchange(true, std::memory_order_acquire)) {// 自旋,直到锁被释放}}void unlock() {// 释放锁flag.store(false, std::memory_order_release);}private:std::atomic<bool> flag; // 锁的标志
};
TAS实现简单,但是由于它是不断尝试TEST_AND_SET,当多个线程在不同的 CPU 核心上竞争同一个 TAS 锁时,所有线程都会频繁地读取和写入锁的状态。这会导致缓存行的频繁失效,因为每次一个线程更新锁的状态时,其他线程的缓存中相应的缓存行会失效,从而引发更高的内存访问延迟。这不仅增加了内存带宽的消耗,还可能导致内存访问争用,进而影响到系统的整体性能。在高竞争环境下,线程自旋等待锁的状态,频繁地访问同一内存地址,可能导致内存访问延迟显著增加。每次缓存失效都需要访问主内存,从而增加了获取锁的时间。
2.2 TTASLock
TTASLock的实现与TASLock类似,只是在获取锁时先测试锁的状态,如果锁已经被占用,则自旋等待,否则尝试获取锁。TTASLock的实现如下所示:
class TTASLock {
public:TTASLock() : flag(false) {}void lock() {// 自旋等待,直到锁未被占用while (true) {// 先测试锁的状态while (flag.load(std::memory_order_acquire)) {// 自旋等待,直到锁被释放}// 尝试获取锁if (!flag.exchange(true, std::memory_order_acquire)) {// 成功获取锁break;}}}void unlock() {// 释放锁flag.store(false, std::memory_order_release);}private:std::atomic<bool> flag; // 锁的标志
};
TTAS 首先测试锁的状态,而不是立即尝试获取锁。只有在确认锁未被占用时,才会进行写操作(即设置 flag),这减少了对共享内存的写入操作,降低了总线争用的频率。由于 TTAS 在尝试获取锁之前先检查锁的状态,只有在锁未被占用时才会进行 CAS 操作,这可以减少其他线程在尝试获取锁时导致的缓存失效,从而提高性能。在多线程竞争不激烈的情况下,TTAS 锁可以更高效地降低自旋带来的 CPU 资源浪费,尤其是在锁的持有时间短的情况下。尽管 TTAS 减少了总线争用,但它仍然是自旋锁。在线程自旋等待锁时,CPU 资源仍然被消耗,特别是在高竞争情况下,可能导致性能下降。TTAS 的实现依赖于原子操作(如 CAS),在某些硬件架构上,这些操作可能仍然导致较高的延迟,尤其是在较高的竞争情况下。
2.3 指数退避锁
指数退避算法是一种在并发系统中,当多个线程争用同一个锁时,用于减少资源竞争的策略。它通过在每次尝试获取锁失败后,等待一段时间再重试,并且等待的时间长度逐渐增加,从而避免系统频繁尝试而导致过载、资源竞争或者死锁。
- 首次尝试: 线程尝试获取锁。
- 失败后等待: 如果获取锁失败,线程会等待一段时间。
- 指数增加: 等待时间会以指数方式增加,例如,第一次等待1毫秒,第二次等待2毫秒,第三次等待4毫秒,以此类推。
- 重试: 等待时间结束后,线程再次尝试获取锁。
- 达到上限: 为了防止等待时间过长,通常会设置一个最大等待时间。当等待时间达到上限时,就不再增加。
- 加入随机抖动: 为了避免多个请求同时重试导致系统再次过载,可以在指数退避的基础上,加入一些随机性,使得重试的时间并不是固定的,而是随机的。
class ExponentialBackoffTTASLock {
public:
ExponentialBackoffTTASLock() {}void lock() {retries = 0;while (flag.test_and_set(std::memory_order_acquire)) {// spin until the lock is releasedbackoff();retries++;}}void unlock() {flag.clear(std::memory_order_release);}private:void backoff() {const int max_retries = 8;if (retries < max_retries) {std::this_thread::yield();} else {auto delay = std::chrono::microseconds(1 << (retries - max_retries));std::this_thread::sleep_for(delay);}}std::atomic_flag flag = ATOMIC_FLAG_INIT;int retries{0};
};
指数后退的随机抖动可以减少多个线程同时重试的情况,从而减少系统的负载。这有助于避免在高竞争情况下,多个线程同时重试获取锁,从而导致系统过载。但是,指数后退的随机抖动也可能导致某些线程的重试时间过长,从而降低系统的性能。因此,在使用指数后退时,需要根据实际情况进行调整,以达到最佳的性能和资源利用率。
2.4 队列锁
2.4.1 数组队列锁
队列锁是一种基于队列的锁实现,它允许多个线程同时尝试获取锁,但是只有一个线程能够成功获取锁。队列锁的实现如下所示:
class QueueLock {
private:static thread_local int mySlotIndex;std::atomic<int> tail;std::vector<std::atomic<bool>> flag;int size;public:QueueLock(int capacity = 128) : size(capacity), tail(0), flag(capacity) {// 初始化 flag 数组for (int i = 0; i < size; ++i) {flag[i].store(false, std::memory_order_relaxed);}flag[0].store(true, std::memory_order_relaxed); // 初始化第一个标志位为 true}void lock() {int slot = tail.fetch_add(1, std::memory_order_relaxed) % size;mySlotIndex = slot; // 存储当前线程的槽位索引// 自旋等待while (!flag[slot].load(std::memory_order_acquire)) {std::this_thread::yield(); // 让出 CPU 时间片}}void unlock() {int slot = mySlotIndex;flag[slot].store(false, std::memory_order_release); // 释放当前槽位// 允许下一个线程获取锁flag[(slot + 1) % size].store(true, std::memory_order_release);}
};// 初始化 thread_local 变量
thread_local int QueueLock::mySlotIndex = 0;
队列锁通过维护一个请求锁的线程队列,保证了线程按照先来后到的顺序获取锁,解决了传统锁可能导致的线程饥饿问题,确保了公平性。但队列锁需要在初始化时确定最大线程数,限制了其扩展性,并且自旋等待的机制在锁竞争激烈时会浪费 CPU 资源。
2.4.2 CLH队列锁
class CLHQNode {
public:std::atomic<bool> locked;CLHQNode() : locked(false) {}
};class CLHLock {
private:std::atomic<CLHQNode*> tail;static thread_local CLHQNode* myPred;static thread_local CLHQNode* myNode;public:CLHLock() : tail(new CLHQNode()) {// 初始化 thread_local 变量 (C++11 以后可以在类外初始化)}~CLHLock() {// 释放 tail 指向的 CLHQNodedelete tail.load();}void lock() {CLHQNode* node = getMyNode(); // 获取当前线程的 CLHQNodenode->locked.store(true, std::memory_order_relaxed); // 设置为 lockedCLHQNode* pred = tail.exchange(node, std::memory_order_acquire); // 获取前驱节点,并将 tail 指向当前节点myPred = pred; // 保存前驱节点// 自旋等待前驱节点释放锁while (pred->locked.load(std::memory_order_acquire)) {std::this_thread::yield(); // 让出 CPU 时间片}}void unlock() {CLHQNode* node = getMyNode(); // 获取当前线程的 CLHQNodenode->locked.store(false, std::memory_order_release); // 释放锁myNode = myPred; // 将 myNode 指向前驱节点,以便下次使用}private:// 获取当前线程的 CLHQNode,如果不存在则创建CLHQNode* getMyNode() {if (!myNode) {myNode = new CLHQNode();}return myNode;}
};
CLH 队列锁通过链表结构维护等待锁的线程队列,解决了传统锁的公平性问题以及普通队列锁需要预先确定最大线程数的限制,实现了更好的可扩展性。然而,CLH 锁仍然存在自旋等待的问题,这会导致 CPU 资源的浪费,并且每个线程都需要额外的内存来存储队列节点。
2.4.3 MCS锁
MCS锁也是一种基于链表的公平锁,它与 CLH 锁类似,但 MCS 锁的节点直接指向后继节点,而不是前驱节点,这使得释放锁的操作更加简单高效。
#pragma once
#include <atomic>
#include <thread>
#include <iostream>class MCSLock {
private:struct MCSNode {std::atomic<bool> locked;MCSNode* next;MCSNode() : locked(false), next(nullptr) {}};std::atomic<MCSNode*> tail;static thread_local MCSNode* myNode; // 线程局部变量public:MCSLock() : tail(nullptr) {}~MCSLock() {// 需要仔细考虑析构函数中的资源释放,避免内存泄漏// 但是由于多线程环境,简单的 delete tail 可能会导致问题// 所以这里选择不释放,或者使用更复杂的机制来保证安全释放}void lock() {MCSNode* qnode = getMyNode(); // 获取当前线程的 MCSNodeMCSNode* pred = tail.exchange(qnode, std::memory_order_acquire); // 获取前驱节点,并将 tail 指向当前节点if (pred != nullptr) {qnode->locked.store(true, std::memory_order_relaxed); // 设置为 lockedpred->next = qnode; // 将前驱节点的 next 指向当前节点// 等待前驱节点释放锁while (qnode->locked.load(std::memory_order_acquire)) {std::this_thread::yield(); // 让出 CPU 时间片}}}void unlock() {MCSNode* qnode = getMyNode(); // 获取当前线程的 MCSNodeif (qnode->next == nullptr) {// 如果没有后继节点if (tail.compare_exchange_strong(qnode, nullptr, std::memory_order_release, std::memory_order_relaxed)) {// 如果成功将 tail 设置为 null,则直接返回return;}// 等待前驱节点填充 next 字段while (qnode->next == nullptr) {std::this_thread::yield(); // 让出 CPU 时间片}}// 释放后继节点的锁qnode->next->locked.store(false, std::memory_order_release);qnode->next = nullptr; // 避免 double free,虽然这里设置为空,但是qnode->next的内存并没有释放,需要注意内存泄漏的问题}private:// 获取当前线程的 MCSNode,如果不存在则创建MCSNode* getMyNode() {if (!myNode) {myNode = new MCSNode();}return myNode;}
};// 初始化 thread_local 变量
thread_local MCSLock::MCSNode* MCSLock::myNode = nullptr;
MCS 锁和 CLH 锁都是公平队列锁,MCS 锁在释放锁时效率更高,因为直接操作后继节点,但代码稍复杂;而 CLH 锁代码更简单,但释放锁时效率稍逊。选择哪种锁取决于具体应用场景,性能要求高则选 MCS,追求简单则选 CLH。它们都解决了传统锁的公平性问题,并具有良好的可扩展性。MCS更适合多核处理器和NUMA架构,因为它可以更好地利用缓存和内存局部性。
2.4.4 时限队列锁
时限队列锁是一种基于队列的锁实现,它允许线程在一定时间内获取锁,如果超过时限仍未获取到锁,则放弃获取。它的实现如下所示:
class TOLock {
private:struct TONode {TONode* pred;TONode() : pred(nullptr) {}};static TONode* AVAILABLE; // 静态成员变量需要在类外初始化std::atomic<TONode*> tail;static thread_local TONode* myNode; // 线程局部变量public:TOLock() : tail(nullptr) {}~TOLock() {// 同样需要考虑内存管理,这里简单处理,可能存在问题}bool try_lock(long long time, std::chrono::milliseconds unit) {auto startTime = std::chrono::steady_clock::now();auto patience = unit;TONode* qnode = new TONode();myNode = qnode; // 设置当前线程的 TONodeqnode->pred = nullptr;TONode* myPred = tail.exchange(qnode, std::memory_order_acquire); // 获取前驱节点,并将 tail 指向当前节点if (myPred == nullptr || myPred->pred == AVAILABLE) {return true; // 成功获取锁}while (std::chrono::steady_clock::now() - startTime < patience) {TONode* predPred = myPred->pred;if (predPred == AVAILABLE) {return true; // 成功获取锁} else if (predPred != nullptr) {myPred = predPred;if (!tail.compare_exchange_weak(qnode, myPred, std::memory_order_release, std::memory_order_relaxed)) {qnode->pred = myPred;}return false; // 获取锁失败,但仍在等待}std::this_thread::yield(); // 让出 CPU 时间片}// 超时,获取锁失败delete qnode; // 释放申请的 TONode 内存return false;}void unlock() {TONode* qnode = myNode; // 获取当前线程的 TONodeif (!tail.compare_exchange_strong(qnode, nullptr, std::memory_order_release, std::memory_order_relaxed)) {qnode->pred = AVAILABLE;}}
};// 初始化静态成员变量
TOLock::TONode* TOLock::AVAILABLE = new TONode(); // 注意内存管理,需要释放
thread_local TOLock::TONode* TOLock::myNode = nullptr;
时限队列锁是在队列锁的基础上增加了超时机制,允许线程在等待锁时设置一个最大等待时间,超时后放弃等待,避免永久阻塞。相比于普通自旋锁,时限队列锁保证了公平性,避免饥饿;相比于普通队列锁,时限队列锁增加了容错性,防止因死锁等原因造成的无限期等待;但相比于无超时机制的锁,实现更复杂,且超时处理可能引入额外的竞争。
2.5 复合锁
复合锁(Composite Lock)是指将多种锁机制结合在一起,以利用各自的优点,从而在不同场景下实现更好的性能和灵活性。所以实际上的复合锁的实现方案众多,根据具体的场景不同而不同。下面的实现结合了 Test-and-Test-and-Set 自旋锁和指数退避策略,以在锁竞争激烈时减少 CPU 占用并提高性能。
class CompositeLock {
private:static const int SIZE = 8; // 示例值,根据实际情况调整static const int MIN_BACKOFF = 1; // 示例值static const int MAX_BACKOFF = 10; // 示例值enum class State {FREE,WAITING,RELEASED,ABORTED};struct CompositeNode {std::atomic<State> state;CompositeNode* pred;CompositeNode() : state(State::FREE), pred(nullptr) {}};class Backoff {private:const int minDelay;const int maxDelay;int limit;std::random_device rd;std::mt19937 gen;std::uniform_int_distribution<> distrib;public:Backoff(int min, int max) : minDelay(min), maxDelay(max), limit(min), rd(), gen(rd()), distrib(min, max) {}void backoff() {std::this_thread::sleep_for(std::chrono::milliseconds(distrib(gen)));limit = std::min(maxDelay, limit * 2);}};std::atomic<CompositeNode*> tail;CompositeNode waiting[SIZE]{};std::random_device rd;std::mt19937 gen;std::uniform_int_distribution<> distrib;static thread_local CompositeNode* myNode; // 线程局部变量bool timeout(long long patience, std::chrono::steady_clock::time_point startTime) {auto now = std::chrono::steady_clock::now();auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - startTime).count();return elapsed > patience;}public:CompositeLock() : tail(nullptr), distrib(0, SIZE - 1) {}~CompositeLock() {}void unlock() {CompositeNode* acqNode = myNode;if (acqNode != nullptr) {acqNode->state.store(State::RELEASED, std::memory_order_release);myNode = nullptr;}}bool try_lock(long long time, std::chrono::milliseconds unit) {auto startTime = std::chrono::steady_clock::now();long long patience = unit.count();Backoff backoff(MIN_BACKOFF, MAX_BACKOFF);try {CompositeNode* node = acquireCompositeNode(backoff, startTime, patience);CompositeNode* pred = spliceCompositeNode(node, startTime, patience);waitForPredecessor(pred, node, startTime, patience);return true;} catch (const std::runtime_error& e) { // 使用 std::runtime_error 捕获异常return false;}}private:CompositeNode* acquireCompositeNode(Backoff& backoff, std::chrono::steady_clock::time_point startTime, long long patience) {while (true) {int index = distrib(gen);CompositeNode& node = waiting[index];State expected = State::FREE;if (node.state.compare_exchange_strong(expected, State::WAITING)) {return &node;}CompositeNode* currTail = tail.load();State state = node.state.load();if (state == State::ABORTED || state == State::RELEASED) {if (&node == currTail) {CompositeNode* myPred = node.pred;if (tail.compare_exchange_weak(currTail, myPred)) {node.state.store(State::WAITING);return &node;}}}backoff.backoff();if (timeout(patience, startTime)) {throw std::runtime_error("TimeoutException");}}}CompositeNode* spliceCompositeNode(CompositeNode* node, std::chrono::steady_clock::time_point startTime, long long patience) {CompositeNode* currTail;do {currTail = tail.load();if (timeout(patience, startTime)) {node->state.store(State::FREE);throw std::runtime_error("TimeoutException");}} while (!tail.compare_exchange_weak(currTail, node));return currTail;}void waitForPredecessor(CompositeNode* pred, CompositeNode* node, std::chrono::steady_clock::time_point startTime, long long patience) {if (pred == nullptr) {myNode = node;return;}State predState = pred->state.load();while (predState != State::RELEASED) {if (predState == State::ABORTED) {CompositeNode* temp = pred;pred = pred->pred;temp->state.store(State::FREE);if (timeout(patience, startTime)) {node->pred = pred;node->state.store(State::ABORTED);throw std::runtime_error("TimeoutException");}predState = pred->state.load();pred->state.store(State::FREE);myNode = node;return;}}}
};thread_local CompositeLock::CompositeNode* CompositeLock::myNode = nullptr;
2.6 层次锁
层次锁是一种通过构建多层锁结构,由粗到细逐层尝试获取,并结合退避策略来减少锁竞争,优化NUMA架构下并发性能的锁机制。
2.6.1 层次后退锁
层次后退锁(Hierarchical Backoff Lock)是一种结合了层次锁和退避策略的锁,旨在优化在多处理器系统中的性能,尤其是在 NUMA(Non-Uniform Memory Access)架构下。 它的核心思想是,将锁的获取过程分为多个层次,每个层次对应不同的锁粒度和竞争范围,在每个层次上,如果获取锁失败,则采用退避策略,例如指数退避,以减少锁冲突。
#pragma once// 假设 ThreadID 类存在,并且可以获取线程的集群 ID
// 这部分依赖于具体的 NUMA 架构和操作系统 API
class ThreadID {
public:static int getCluster() {// 实际实现需要调用 NUMA API 或其他方式获取线程的集群 ID// 这里只是一个示例,返回一个随机值static std::random_device rd;static std::mt19937 gen(rd());static std::uniform_int_distribution<> distrib(0, 1); // 假设只有两个集群return distrib(gen);}
};class HierarchicalBackoffLock {
private:static const int LOCAL_MIN_DELAY = 1; // 示例值static const int LOCAL_MAX_DELAY = 10; // 示例值static const int REMOTE_MIN_DELAY = 10; // 示例值static const int REMOTE_MAX_DELAY = 100; // 示例值static const int FREE = 0;std::atomic<int> state;class Backoff {private:const int minDelay;const int maxDelay;int limit;std::random_device rd;std::mt19937 gen;std::uniform_int_distribution<> distrib;public:Backoff(int min, int max) : minDelay(min), maxDelay(max), limit(min), rd(), gen(rd()), distrib(min, max) {}void backoff() {std::this_thread::sleep_for(std::chrono::microseconds(distrib(gen))); // 使用 us 级别延迟limit = std::min(maxDelay, limit * 2);}};public:HierarchicalBackoffLock() : state(FREE) {}void lock() {int myCluster = ThreadID::getCluster();Backoff localBackoff(LOCAL_MIN_DELAY, LOCAL_MAX_DELAY);Backoff remoteBackoff(REMOTE_MIN_DELAY, REMOTE_MAX_DELAY);while (true) {auto free = FREE;if (state.compare_exchange_weak(free, myCluster)) {return; // 成功获取锁}int lockState = state.load();if (lockState == myCluster) {localBackoff.backoff(); // 本地退避} else {remoteBackoff.backoff(); // 远程退避}}}void unlock() {state.store(FREE, std::memory_order_release);}
};
2.6.2 层次 CLH 队列锁
层次 CLH 队列锁的基本思想是在 NUMA (Non-Uniform Memory Access) 架构下,构建一个层次化的 CLH 队列锁结构,使得线程尽可能在本地 NUMA 节点上排队等待,减少跨节点访问,从而提高性能,是一种结合了 CLH 队列锁的公平性和层次锁的 NUMA 感知特性的锁。通过维护多个 CLH 队列,每个队列对应一个 NUMA 节点,线程在本地节点上排队,当本地队列为空时,尝试在全局队列上排队。这种分层设计可以减少跨节点访问,提高性能。
// 假设 HCLHThreadID 类存在,并且可以获取线程的集群 ID
// 这部分依赖于具体的 NUMA 架构和操作系统 API
class HCLHThreadID {
public:static int getCluster() {// 实际实现需要调用 NUMA API 或其他方式获取线程的集群 ID// 这里只是一个示例,返回一个随机值static std::random_device rd;static std::mt19937 gen(rd());static std::uniform_int_distribution<> distrib(0, 1); // 假设只有两个集群return distrib(gen);}
};class HierarchicalCLHLock {
private:static const int MAX_CLUSTERS = 2; // 示例值,根据实际情况调整struct QNode {static const long long TWS_MASK = 0x8000000000;static const long long SMW_MASK = 0x4000000000;static const long long CLUSTER_MASK = 0x3FFFFFFFFF;std::atomic<long long> state; // Use long long to store the stateQNode() : state(0) {}void unlock() {int myCluster = HCLHThreadID::getCluster();long long oldState = 0;long long newState = myCluster;newState |= SMW_MASK;newState &= (~TWS_MASK);do {oldState = state.load();} while (!state.compare_exchange_weak(oldState, newState));}int getClusterID() {return state.load() & CLUSTER_MASK;}// Setters and Getters for TWS and SMW (omitted for brevity)void setTailWhenSpliced(bool value) {long long oldState = state.load();long long newState = oldState;if (value) {newState |= TWS_MASK;}else {newState &= ~TWS_MASK;}while (!state.compare_exchange_weak(oldState, newState));}bool isSuccessorMustwait() {return (state.load() & SMW_MASK) != 0;}void setSuccessorMustWait(bool value) {long long oldState = state.load();long long newState = oldState;if (value) {newState |= SMW_MASK;}else {newState &= ~SMW_MASK;}while (!state.compare_exchange_weak(oldState, newState));}bool waitforGrantorClusterMaster() {std::this_thread::sleep_for(std::chrono::microseconds(10)); // 模拟等待return true; // 简化,始终返回 true}};std::vector<std::atomic<QNode*>> localQueues{ MAX_CLUSTERS };std::atomic<QNode*> globalQueue;static thread_local QNode* currNode;static thread_local QNode* predNode;public:HierarchicalCLHLock() {globalQueue.store(new QNode());for (int i = 0; i < MAX_CLUSTERS; i++) {localQueues[i].store(nullptr); // 初始化为 nullptr}}~HierarchicalCLHLock() {// 需要考虑内存管理,这里省略}void lock() {QNode* myNode = getCurrNode(); // 获取当前线程的 QNodeint clusterID = HCLHThreadID::getCluster();std::atomic<QNode*>& localQueue = localQueues[clusterID];QNode* myPred = nullptr;do {myPred = localQueue.load();} while (!localQueue.compare_exchange_weak(myPred, myNode));if (myPred != nullptr) {bool iOwnLock = myPred->waitforGrantorClusterMaster();if (iOwnLock) {predNode = myPred;return;}}// I am the cluster master: splice local queue into global queueQNode* localTail = nullptr;do {myPred = globalQueue.load();localTail = myNode; // 这里直接使用 myNode,因为 localQueue 已经指向 myNode} while (!globalQueue.compare_exchange_weak(myPred, localTail));// inform successor it is the new masterlocalTail->setTailWhenSpliced(true);while (myPred->isSuccessorMustwait()) {};predNode = myPred;return;}void unlock() {QNode* myNode = currNode;if (myNode != nullptr) {myNode->setSuccessorMustWait(false);QNode* node = predNode;if (node != nullptr) {node->unlock();}currNode = node;}}private:// 获取当前线程的 QNode,如果不存在则创建QNode* getCurrNode() {if (!currNode) {currNode = new QNode();}return currNode;}
};thread_local HierarchicalCLHLock::QNode* HierarchicalCLHLock::currNode = nullptr;
thread_local HierarchicalCLHLock::QNode* HierarchicalCLHLock::predNode = nullptr;
3 参考文献
- 现代 C++ 中的自旋锁,具有原子性、内存屏障和指数退避
- 排队自旋锁
- 多处理器编程的艺术
相关文章:
自旋锁(C++实现)
1 简介 自旋锁是一种典型的无锁算法,在任何时刻,它都最多只能有一个保持者。当有一个线程试图获得一个已经被占用的锁时,该线程就会一直进行循环等待,直到该锁被释放。自旋锁的优点是不会使线程状态发生切换,一直处于用…...
python基础-11-调试程序
文章目录 【README】【11】调试【11.1】抛出异常【11.1.1】抛出异常代码块 【11.2】获取回溯字符串【11.2.1】traceback获取异常回溯信息 【11.3】断言【11.3.1】断言代码示例 【11.4】日志(使用logging模块)【11.4.1】使用logging模块操作日志【11.4.3】…...
我的创作历程:从不情愿到主动分享的成长
🌅主页:猫咪-9527-CSDN博客 “欲穷千里目,更上一层楼。会当凌绝顶,一览众山小。” 目录 二、转变:从被动到主动的心路历程 三、挑战:时间的压力与写作的坚持 四、收获:分享与成长 五、展望…...
uniapp地图导航及后台百度地图回显(v2/v3版本)
百度地图申请地址:控制台 | 百度地图开放平台 效果: 1.后台 1.1申请百度地图APi 1.2 引入百度地图 <script type"text/javascript" src"//api.map.baidu.com/api?v3.0&ak自己百度生气apikey"></script> 1.3 v2组…...
多layout 布局适配
安卓多布局文件适配方案操作流程 以下为通过多套布局文件适配不同屏幕尺寸/密度的详细步骤,结合主流适配策略及最佳实践总结: 一、创建多套布局资源目录 按屏幕尺寸划分 在 res 目录下创建以下文件夹(根据设备特性自动匹配ÿ…...
马斯克 AI 超算
超算建设情况:马斯克旗下人工智能初创公司 xAI 正在田纳西州孟菲斯市建造世界上最大的超级计算机2。自 2024 年 6 月首次宣布这笔工程以来,xAI 已向当地规划和发展机构提交了 14 份施工许可申请,合计代表了 4.059 亿美元的预计项目成本2。该超…...
大模型学习四:DeepSeek Janus-Pro 多模态理解和生成模型 本地部署指南(折腾版)
一、说明简介 DeepSeek Janus-Pro是一款先进的多模态理解和生成模型,旨在实现高质量的文本-图像生成与多模态理解。它是由DeepSeek团队研发的,是之前Janus模型的升级版,能够同时处理文本和图像,即可以理解图片内容,…...
《AI大模型应知应会100篇》第3篇:大模型的能力边界:它能做什么,不能做什么
第3篇:大模型的能力边界:它能做什么,不能做什么 摘要 在人工智能飞速发展的今天,大语言模型(LLM)已经成为许多领域的核心技术。然而,尽管它们展现出了惊人的能力,但也有明显的局限性…...
MySQL 面试知识点详解(索引、存储引擎、事务与隔离级别、MVCC、锁机制、优化)
一、索引基础概念 1 索引是什么? 定义:索引是帮助MySQL高效获取数据的有序数据结构,类似书籍的目录。核心作用:减少磁盘I/O次数,提升查询速度(以空间换时间)。 2 索引的优缺点 优点缺点加速…...
JS API
const变量优先 即对象、数组等引用类型数据可以用const声明 API作用和分类 DOM (ducument object model) 操作网页内容即HTML标签的 树状模型 HTML中标签 JS中对象 最大对象 document 其次大 html 以此类推 获取DOM对象 CSS 中 使用选择器 JS 中 选多个 时代的眼泪 修…...
hackmyvm-Principle
近况: 很难受、 也很累。 但是庆幸靶机很好 正值清明时节 清明时节雨纷纷 🌧️,路上行人欲断魂 😢。 靶机地址 信息收集 主机发现 端口扫描 80端口仅仅是一个nginx 的欢迎界面而已 robots.txt的内容 hi.html的内容 hackme不存在 investigat…...
小刚说C语言刷题——第14讲 逻辑运算符
当我们需要将一个表达式取反,或者要判断两个表达式组成的大的表达式的结果时,要用到逻辑运算符。 1.逻辑运算符的分类 (1)逻辑非(!) !a,当a为真时,!a为假。当a为假时,!a为真。 例…...
池化技术的深度解析与实践指南【大模型总结】
池化技术的深度解析与实践指南 池化技术作为计算机系统中的核心优化手段,通过资源复用和预分配机制显著提升系统性能。本文将从原理、实现到最佳实践,全方位剖析池化技术的核心要点,并结合实际案例说明其应用场景与调优策略。 一、池化技术的…...
基于Java的区域化智慧养老系统(源码+lw+部署文档+讲解),源码可白嫖!
摘 要 时代在飞速进步,每个行业都在努力发展现在先进技术,通过这些先进的技术来提高自己的水平和优势,区域化智慧养老系统当然不能排除在外。区域化智慧养老系统是在实际应用和软件工程的开发原理之上,运用Java语言、JSP技术以及…...
2025年3月 Scratch 图形化(一级)真题解析 中国电子学会全国青少年软件编程等级考试
2025.03 Scratch图形化编程等级考试一级真题试卷 一、选择题 第 1 题 气球初始位置如下图所示,scratch运行下列程序,气球会朝哪个方向移动?( ) A.水平向右 B.垂直向下 C.水平向左 D.垂直向上 答案:…...
Docker 命令简写配置
alias dpsdocker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}" 配置好后,需要输入: source ~/.bashrc 后生效...
linux signal up/down/down_interruptiable\down_uninterruptiable使用
在Linux内核中,down, down_interruptible, down_killable, 和 up 是用于操作信号量(semap hores)的函数,它们用于进程同步和互斥。以下是对这些函数的简要说明。 1,down(&sem): 这个函数用于获取信号量。如果信号…...
【嵌入式-stm32电位器控制以及旋转编码器控制LED亮暗】
嵌入式-stm32电位器控制LED亮暗 任务1代码1Key.cKey.hTimer.cTimer.hPWM.cPWM.hmain.c 实验现象1任务2代码2Key.cKey.hmain.c 实验现象2问题与解决总结 源码框架取自江协科技,在此基础上做扩展开发。 任务1 本文主要介绍利用stm32f103C8T6实现电位器控制PWM的占空比…...
Mysql 中 ACID 背后的原理
在 MySQL 中,ACID 是事务处理的核心原则,用于保证数据库在执行事务时的可靠性、数据一致性和稳定性。ACID 是四个关键特性的首字母缩写,分别是:Atomicity(原子性)、Consistency(一致性ÿ…...
【算法】简单数论
模运算 a m o d b a − ⌊ a / b ⌋ b a\ mod \ b a - \lfloor a / b \rfloor \times b a mod ba−⌊a/b⌋b n m o d p n \ mod\ p n mod p得到的结果的正负至于被除数 n n n有关 模运算的性质: ( a b ) m o d m ( ( a m o d m ) ( b m o d m ) ) m o d m …...
mybatis慢sql无所遁形
痛点问题: 扫描项目的慢sql 并提出优化建议 开源项目地址:gitee:mybatis-sql-optimizer-spring-boot-starter: 这个starter可以帮助开发者在开发阶段发现SQL性能问题,并提供优化建议,从而提高应用程序的数据库访问效…...
MCP有哪些比较好的资源?
MCP(Model Context Protocol)是一种由Anthropic公司推出的开放协议,旨在为AI模型与开发环境之间提供统一的上下文交互接口。目前,围绕MCP协议的资源非常丰富,以下是一些比较好的MCP资源推荐: Smithery Smit…...
Nginx功能及应用全解:从负载均衡到反向代理的全面剖析
Nginx作为一款开源的高性能HTTP服务器和反向代理服务器,凭借其高效的资源利用率和灵活的配置方式,已成为互联网领域中最受欢迎的Web服务器之一。无论是作为HTTP服务器、负载均衡器,还是作为反向代理和缓存服务器,Nginx的多种功能广…...
FreeRTOS/任务创建和删除的API函数
任务的创建和删除本质就是调用FreeRTOS的API函数 API函数描述xTaskCreate()动态方式创建任务xTaskCreateStatic()静态方式创建任务vTaskDelete()删除任务 动态创建任务 任务的任务控制块以及任务的占空间所需的内存,均由FreeRTOS从FreeRTOS管理的堆中分配 静态创建…...
【jvm】GC评估指标
目录 1. 说明2. 吞吐量(Throughput)3. 暂停时间(Pause Time)4. 内存占用(Footprint)5. 频率(Frequency)6. 对象晋升率(Promotion Rate)7. 内存分配速率&#…...
数据集(Dataset)和数据加载器(DataLoader)-pytroch学习3
pytorch网站学习 处理数据样本的代码往往会变得很乱、难以维护;理想情况下,我们希望把数据部分的代码和模型训练部分分开写,这样更容易阅读、也更好维护。 简单说:数据和模型最好“分工明确”,不要写在一起。 PyTor…...
影响RTOS实时性的因素有哪些?
目录 1、任务调度延迟 2、中断处理延迟 3、系统负载 4、任务优先级反转 5、时钟精度 6、内存管理 影响RTOS实时性的因素主要包括任务调度延迟、中断处理延迟、系统负载、任务优先级反转、时钟精度、内存管理等。 1、任务调度延迟 任务调度器是RTOS的核心,当…...
二叉树 递归
本篇基于b站灵茶山艾府的课上例题与课后作业。 104. 二叉树的最大深度 给定一个二叉树 root ,返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1: 输入:root [3,9,20,null,null,15,7] 输出&…...
ZLMediaKit 源码分析——[5] ZLToolKit 中EventPoller之延时任务处理
系列文章目录 第一篇 基于SRS 的 WebRTC 环境搭建 第二篇 基于SRS 实现RTSP接入与WebRTC播放 第三篇 centos下基于ZLMediaKit 的WebRTC 环境搭建 第四篇 WebRTC学习一:获取音频和视频设备 第五篇 WebRTC学习二:WebRTC音视频数据采集 第六篇 WebRTC学习三…...
【51单片机】2-6【I/O口】电动车简易防盗报警器实现
1.硬件 51最小系统继电器模块震动传感器模块433M无线收发模块 2.软件 #include "reg52.h" #include<intrins.h> #define J_ON 1 #define J_OFF 0sbit switcher P1^0;//继电器 sbit D0_ON P1^1;//433M无线收发模块 sbit D1_OFF P1^2; sbit vibrate …...
windows下载安装远程桌面工具RealVNC-Server教程(RealVNC_E4_6_1版带注册码)
文章目录 前言一、下载安装包二、安装步骤三、使用VNC-Viewer客户端远程连接,输入ip地址,密码完成连接 前言 在现代工作和生活中,远程控制软件为我们带来了极大的便利。RealVNC - Server 是一款功能强大的远程控制服务器软件,通过…...
C语言的操作系统
C语言的操作系统 引言 操作系统是一种系统软件,它管理计算机硬件和软件资源,并为计算机程序提供公共服务。在现代计算机科学中,操作系统是不可或缺的组成部分,而C语言则是实现高效操作系统的主要编程语言之一。本文将探讨C语言在…...
selectdb修改表副本
如果想修改doris(也就是selectdb数据库)表的副本数需要首先确定是否分区表,当前没有数据字典得知哪个表是分区的,只能先show partitions看结果 首先,副本数不应该大于be节点数 其次,修改期间最好不要跑业务…...
leetcode数组-有序数组的平方
题目 题目链接:https://leetcode.cn/problems/squares-of-a-sorted-array/ 给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。 输入:nums [-4,-1,0,3,10] 输出ÿ…...
【python中级】关于Cython 的源代码pyx的说明
【python中级】关于Cython 的源代码pyx的说明 1.背景2.编译3.语法1.背景 Cython 是一个编程语言和工具链,用于将 Python 代码(或类 Python 的代码)编译成 C 语言,再进一步生成高性能的 Python 扩展模块(.so 或 .pyd 文件)。 在 Python 中,.pyx 文件是 Cython 的源代码文…...
开放最短路径优先 - OSPF【LSA详细】
目录 LSA的头部结构 LSA类型 LSA数据包 LSA的主要作用是传递路由信息。 LSA的头部结构 共占20个字节,不同类型的LSA头部字段部分都是相同的。 链路状态老化时间(Link-State Age) 2个字节。指示该条LSA的老化时间,即它存在了多长时间,单位…...
PyTorch:解锁AI新时代的钥匙
揭开PyTorch面纱 对于许多刚开始接触人工智能领域的朋友来说,PyTorch这个名字或许既熟悉又陌生。熟悉在于它频繁出现在各类技术论坛和新闻报道中;而陌生则源于对这样一个强大工具背后运作机制的好奇。简单来说,PyTorch是一个开源库ÿ…...
欧几里得算法求最大公约数、最小公倍数
这段代码就是不断用较小数和余数来更新 a 和 b,直到余数变为 0,最后返回的 a 就是最大公约数。 #include <iostream> using namespace std;//最大公约数 int gcd(int a, int b){//这个循环表示只要 b 不是 0,就继续进行。//因为当 b …...
QEMU源码全解析 —— 块设备虚拟化(14)
接前一篇文章:QEMU源码全解析 —— 块设备虚拟化(13) 本文内容参考: 《趣谈Linux操作系统》 —— 刘超,极客时间 《QEMU/KVM源码解析与应用》 —— 李强,机械工业出版社 特此致谢! 上一回开始解析VirtioDeviceClass的realize函数virtio_blk_device_realize(),再来回…...
深入理解AOP:面向切面编程的核心概念与实战应用
🌟 前言 欢迎来到我的技术小宇宙!🌌 这里不仅是我记录技术点滴的后花园,也是我分享学习心得和项目经验的乐园。📚 无论你是技术小白还是资深大牛,这里总有一些内容能触动你的好奇心。🔍 &#x…...
3500 阶乘求和
3500 阶乘求和 ⭐️难度:中等 🌟考点:2023、思维、省赛 📖 📚 import java.util.Scanner;public class Main {public static void main(String[] args) {long sum 0;for(int i1;i<50;i) { // 之后取模都相等su…...
正则入门到精通
一、正则表达式入门 正则表达式本质上是一串字符序列,用于定义一个文本模式。通过这个模式,我们可以指定要匹配的文本特征。例如,如果你想匹配一个以 “abc” 开头的字符串,正则表达式可以写作 “^abc”,其中 …...
Mysql 行级锁在什么样的情况下会升级为表级锁?
在 MySQL 中,行级锁通常由 InnoDB 存储引擎使用,因为它支持高并发和细粒度的锁定。通常情况下,InnoDB 在执行诸如 UPDATE、DELETE 或 SELECT FOR UPDATE 等操作时,会为被修改的数据行加锁(行级锁)。但是&am…...
docker部署kkfileview
拉取 KKFileView 镜像 docker pull keking/kkfileview或指定版本 docker pull keking/kkfileview:4.1.0运行 KKFileView 容器 docker run -d \--name kkfileview \-p 8012:8012 \--restart always \keking/kkfileview-d:后台运行 -p 8012:8012:将容器…...
优选算法的妙思之流:分治——快排专题
专栏:算法的魔法世界 个人主页:手握风云 目录 一、快速排序 二、例题讲解 2.1. 颜色分类 2.2. 排序数组 2.3. 数组中的第K个最大元素 2.4. 库存管理 III 一、快速排序 分治,简单理解为“分而治之”,将一个大问题划分为若干个…...
蓝桥杯嵌入式第15届真题-个人理解+解析
个人吐槽 #因为最近蓝桥杯快要开始了,我舍不得米白费了,所以就认真刷刷模拟题,但是我感觉真题会更好,所以就看了一下上届的真题。不过它是真的长,我看着就头晕,但是还是把几个模块认真分析了一下就还是很容…...
数据库系统概述 | 第二章课后习题答案
本文为数据库系统概论(第五版)【高等教育出版社】部分课后答案 如有错误,望指正 👻 习题 👻 答案...
深入解析CPU主要参数:选购与性能评估指南
引言 中央处理器(CPU)作为计算机的"大脑",其性能直接决定了整机的运算能力和响应速度。无论是组装新电脑、升级旧系统还是选购笔记本电脑,理解CPU的关键参数都至关重要。本文将从技术角度全面解析CPU的各项主要参数&am…...
Lettuce与Springboot集成使用
一、Lettuce核心优势与Spring Boot集成背景 Lettuce特性 基于Netty的非阻塞I/O模型,支持同步/异步/响应式编程线程安全:共享单连接实现多线程并发操作,性能衰减低原生支持Redis集群、哨兵、主从架构,自动重连机制保障高可用Spring…...
【Kafka基础】ZooKeeper在Kafka中的核心作用:分布式系统中枢神经系统
在分布式系统的世界里,协调和管理多个节点间的状态是一项复杂而关键的任务。Apache Kafka作为一款高性能的分布式消息系统,其设计哲学是"专为单一目的而优化"——即高效处理消息流。为了实现这一目标,Kafka选择将集群协调管理的重任…...