5 独占队列(Exclusive queue solution)

参考 https://blog.youkuaiyun.com/booksyhay/article/details/82696779

信号量也可用于表示队列。在这种情况下,初始值为0,并且通常编写代码以确保不会发出信号,除非有线程在等待。因此信号量的值永远不会为正数。

例如,假设线程代表舞厅舞者,并且有两种舞者:领舞(leaders)和伴舞(followers),在进入舞池之前排队等候。 当领舞到达时,它会检查是否有伴舞在等待。如果是,他们就都可以进去。 否则就要等待。

类似地,当伴舞到达时,它会检查是否有领舞,相应地,选择继续或等待。

初始化
在这里插入图片描述
leaders和followers是计数器,记录每种等待的舞者的数量。 mutex保证对计数器的独占访问。

leaderQueue和followerQueue是舞者等待的队列。 rendezvous用于检查两个线程是否已完成跳舞。

解决方案
leaders
在这里插入图片描述
当领导者leader到达时,它会获得保护leaders和followers变量的互斥体mutex。 如果有追随者follower在等待,领导者会减少followers计数,给追随者发送信号,然后在释放互斥锁mutex之前调用dance()。 这保证了只有一个追随者线程同时运行dance()。

如果没有追随者在等待,领导者必须在等待leaderQueue之前释放互斥锁。
原文链接:https://blog.youkuaiyun.com/booksyhay/article/details/82696779
followers
在这里插入图片描述
当追随者到达时,它会检查等待的领导者。 如果有一个,那么追随者会减少领导者计数leaders,向领导者发出信号并执行dance,而不释放互斥体mutex。 实际上,在这种情况下,追随者永远不会释放互斥锁; 而领导者会。 我们不必跟踪哪个线程具有互斥锁,因为我们知道其中有一个有,并且其中任何一个都可以释放它。 在我的解决方案中,它始终是领导者。

当一个信号量被用作队列时【用作队列的信号量与条件变量非常相似。 主要区别在于线程必须在等待之前显式释放互斥锁,并在之后显式重新获取它(但仅在它们需要时才重新获取)。】,我发现这样很有用:将“等待”看作“等待这个队列”,“发送信号”看作“让这个队列中有人离开”。

在这段代码中,除非有人在等待,否则我们永远不会发出队列信号,因此队列信号量的值很少是正数。 但是有可能。 看看你是否能弄明白。
原文链接:https://blog.youkuaiyun.com/booksyhay/article/details/82696779

osSemaphoreId_t sem_mutex;
sem_mutex = osSemaphoreNew(1, 1, NULL);

osSemaphoreId_t sem_leaders;
sem_leaders = osSemaphoreNew(1, 0, NULL);

osSemaphoreId_t sem_followers;
sem_followers = osSemaphoreNew(1, 0, NULL);

osSemaphoreId_t sem_rendezvous;
sem_rendezvous = osSemaphoreNew(1, 0, NULL);

int count = 0;
int leaders = 0;
int followers = 0;

void dance()
{
	static int num = 0;
	num++;
    printf("num = %d \r\n", num); 
}

leaders线程

void func_leaders(void)
{
	osSemaphoreAcquire(sem_mutex, osWaitForever);
	if(followers > 0)
	{
		followers--;
        osSemaphoreRelease(sem_followers);
	}
	else
	{
		leaders++;
		osSemaphoreRelease(sem_mutex);
		osSemaphoreAcquire(sem_leaders, osWaitForever);
	}
	dance();
	osSemaphoreAcquire(sem_rendezvous, osWaitForever);
	osSemaphoreRelease(sem_mutex);
	printf("rendezvous\r\n");
}

followers线程

void func_followers(void)
{
	osSemaphoreAcquire(sem_mutex, osWaitForever);
	if(leaders > 0)
	{
		leaders--;
		osSemaphoreRelease(sem_leaders);
	}
	else
	{
		followers++;
		osSemaphoreRelease(sem_mutex);
		osSemaphoreAcquire(sem_followers, osWaitForever);
	}
	dance();
	osSemaphoreRelease(sem_rendezvous);
}

创建2个leaders线程,3个followers线程

结论:
可以看到,即使有2个leaders和3个followers,但是每次都只有一个leader和一个follower一起进入舞池跳舞;
在这里插入图片描述

<think>好的,用户现在想了解ARMv8中的独占访问(exclusive)机制。之前他问过可共享内存,现在这个问题也是内存管理的一部分,但更偏向于同步机制。首先,我需要确认用户的需求。他可能是在多核编程或者需要处理并发时遇到了问题,比如如何保证原子操作,避免数据竞争。 用户可能已经知道可共享内存的概念,现在想知道ARMv8如何实现独占访问来确保数据一致性。需要解释清楚LDXR和STXR指令的工作原理,以及它们如何配合使用来实现原子操作。同时,用户可能对独占监视器(Exclusive Monitor)不太了解,需要详细说明其作用,比如跟踪独占访问的状态。 还要注意用户的应用场景,比如是在操作系统开发、驱动编写还是应用层多线程编程。不同的场景可能需要不同的细节。例如,操作系统开发者可能更关心监视器的状态管理和上下文切换的影响,而应用开发者可能更关注如何使用这些指令实现锁或原子变量。 需要对比其他架构,比如x86的CAS指令,帮助用户理解ARMv8的不同之处。此外,用户可能想知道实际使用中的注意事项,比如循环重试、内存屏障的使用,以及缓存一致性对独占访问的影响。还要提到异常处理时监视器状态的失效情况,避免潜在的竞态条件。 最后,确保回答结构清晰,分步骤讲解,可能用代码示例说明LDXR和STXR的使用,强调独占访问在实现锁、无锁数据结构中的重要性。同时,检查是否有遗漏点,比如监视器的硬件实现细节,或者在不同ARMv8处理器中的行为差异,确保信息准确可靠。</think>--- ### ARMv8 独占访问(Exclusive Access)机制详解 在ARMv8架构中,独占访问(Exclusive Access)是实现**原子操作**和**无锁同步**的核心机制,通过`LDXR`(Load Exclusive Register)和`STXR`(Store Exclusive Register)指令实现。以下是其工作原理与应用场景的逐步解析: --- ### 一、独占访问的核心目标 - **原子性**:确保对内存的“读-修改-写”操作在多核/多线程环境中不可分割。 - **无锁同步**:避免传统锁(如自旋锁)的开销,提升并发性能。 --- ### 二、关键组件:独占监视器(Exclusive Monitor) ARMv8为每个核维护一个**独占监视器**,跟踪独占访问状态: 1. **本地独占监视器**(Local Monitor) - 每个核独立维护,标记当前核的独占访问状态。 2. **全局独占监视器**(Global Monitor) - 针对特定内存地址的全局状态(仅在多核系统中存在)。 --- ### 三、独占访问指令流程 #### 1. **加载独占(LDXR)** - **操作**:从内存地址读取值到寄存器,并标记该地址为“独占访问状态”。 - **示例**: ```assembly LDXR X0, [X1] // 从X1指向的地址加载值到X0,并激活独占监视器 ``` #### 2. **修改数据** - 在本地寄存器中执行计算(如递增、位操作)。 ```assembly ADD X0, X0, #1 // 对X0的值加1 ``` #### 3. **存储独占(STXR)** - **操作**:尝试将寄存器值写回内存地址。 - 若监视器状态有效(期间无其他核修改该地址),则写入成功,返回`0`。 - 若监视器失效(其他核已修改地址),则写入失败,返回`1`。 - **示例**: ```assembly STXR W2, X0, [X1] // 尝试将X0写入[X1],结果状态存入W2 ``` #### 4. **重试机制** - 若`STXR`失败(W2≠0),需重新执行`LDXR→修改→STXR`流程。 ```assembly retry_loop: LDXR X0, [X1] ADD X0, X0, #1 STXR W2, X0, [X1] CBNZ W2, retry_loop // 若失败则重试 ``` --- ### 四、独占访问的特性 1. **地址粒度** - 独占监视器以**自然对齐的地址块**(通常为缓存行大小,如64字节)为单位跟踪状态。 - 若同一块内任一地址被修改,监视器均会失效。 2. **上下文切换影响** - 进程切换或异常(如中断)会导致独占监视器状态**被清除**,需重新尝试。 3. **多核一致性** - 全局独占监视器通过缓存一致性协议(如MESI)维护多核间的状态同步。 --- ### 五、应用场景 #### 1. 原子变量操作 - 实现原子计数器、标志位等: ```c // C内联汇编实现原子递增 void atomic_inc(uint64_t *addr) { uint32_t status; do { asm volatile("ldxr x0, [%1]\n" "add x0, x0, #1\n" "stxr %w0, x0, [%1]" : "=r"(status) : "r"(addr) : "x0", "memory"); } while (status != 0); } ``` #### 2. 自旋锁(Spinlock) - 基于独占访问实现轻量级锁: ```assembly acquire_lock: LDXR X0, [X1] // 加载锁状态 CBNZ X0, acquire_lock // 若锁被占用则重试 MOV X0, #1 STXR W2, X0, [X1] // 尝试获取锁 CBNZ W2, acquire_lock RET release_lock: STLR XZR, [X1] // 使用Store-Release指令释放锁 RET ``` #### 3. 无锁数据结构 - 实现无锁队列、栈等,避免锁争用。 --- ### 六、与x86架构对比 | 特性 | ARMv8 (LDXR/STXR) | x86 (CMPXCHG) | |---------------------|-----------------------------|----------------------------| | **实现方式** | 独占监视器 + 重试循环 | 锁总线或缓存锁 | | **粒度** | 地址块(如缓存行) | 单个内存地址 | | **性能优化** | 冲突少时效率高 | 高竞争时可能更优 | | **指令扩展** | LDAXR/STLXR(Acquire/Release语义) | LOCK前缀指令 | --- ### 七、关键注意事项 1. **内存屏障** - 使用`LDAR`(Load-Acquire)和`STLR`(Store-Release)保证内存顺序。 - 示例:释放锁时需确保之前的写操作对其他核可见。 ```assembly STLR X0, [X1] // Store-Release语义 ``` 2. **缓存对齐** - 独占访问地址应缓存对齐,避免伪共享(False Sharing)。 3. **短临界区** - 独占访问适合轻量级操作,长时间占用会导致重试开销增加。 --- ### 八、总结 ARMv8的独占访问机制通过硬件级监视器和`LDXR/STXR`指令,为多核环境提供了高效的原子操作支持。其核心优势在于: - **低开销**:无需锁总线,减少争用。 - **灵活性**:适用于自旋锁、无锁算法等多种场景。 - **可扩展性**:结合内存屏障指令,支持复杂的内存顺序模型。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值