使用二进制信号量创建互斥锁

时间:2013-10-04 19:46:28

标签: c++ multithreading embedded

我正在使用一个没有互斥锁的简单系统,而是一组有限的硬件二进制信号量。通常,所有多线程都是使用繁重的信号量技术完成的,这些技术会使代码性能下降并且在没有死锁的情况下难以正确编写。

一个简单的实现是全局使用一个信号量,以确保对关键部分的原子访问。但是,这意味着如果正在访问任何关键部分,不相关的对象(甚至是不同类型的对象)将会阻塞。

我目前解决这个问题的方法是使用单个全局信号量来确保对保护字节的原子访问,然后确保对特定关键部分的原子访问。到目前为止,我目前有这个:

while (true) {
    while (mutexLock == Mutex::Locked) {
    } //wait on mutex
    Semaphore semaLock(SemaphoreIndex::Mutex); //RAII semaphore object
    if (mutexLock == Mutex::Unlocked) {
        mutexLock = Mutex::Locked;
        break;
    }
} //Semaphore is released by destructor here
// ... atomically safe code
mutexLock = Mutex::Unlocked;

我有几个问题:这是解决此问题的最佳方法吗?这段代码是线程安全的吗?这与“双重检查锁”相同吗?如果是这样,它是否会遇到同样的问题,因此需要记忆障碍?

编辑:有关系统的一些注意事项正在实施......

它是一个带有32kB RAM的RISC 16位处理器。虽然它具有繁重的多线程功能,但其内存模型非常原始。加载和存储是原子的,没有缓存,没有分支预测或分支目标预测,一个核心有许多线程。内存障碍主要是让编译器知道它应该将内存重新加载到通用寄存器中,而不是出于任何硬件原因(没有缓存)

2 个答案:

答案 0 :(得分:1)

即使处理器具有繁重的多线程功能(不确定你的意思是什么),但它是单核处理器,它仍然意味着任何时候只能执行一个线程。

必须使用任务切换系统(通常以特权模式运行)使用此系统,您必须能够定义关键部分以原子方式执行(软件实现的)互斥锁定/解锁。

当你说"一个核心,多个线程"这是否意味着您的处理器上运行了某种内核?任务切换系统将由该内核实现。 查看内核文档或向供应商询问任何提示可能是值得的。

祝你好运。

答案 1 :(得分:1)

目前我们还没有关于你的系统的那些信息(例如,并行的每条指令可以使用哪种寄存器?你使用银行架构吗?你可以实际执行多少条同步指令?)但希望我的建议能帮到你

如果我了解你的情况,你有一块没有真正核心的硬件,而只是通过矢量化操作(基于你的回复)的MIMD能力。它是一个带有32kB RAM的#34; RISC 16位处理器"其中:

  

加载和存储是原子的,没有缓存,没有分支预测或分支目标预测,一个核心有很多线程

这里的关键是加载和存储是原子的。请注意,您无法以大于16位的方式加载并以原子方式存储,因为它们将被编译为两个单独的原子指令(因此不是原子本身)。

以下是互斥锁的功能:

  • 尝试锁定
  • 解锁

要锁定,如果每个资源都试图锁定,您可能会遇到问题。例如,在硬件中说N = 4(以并行方式运行的进程数)。如果指令1(I1)和I2试图锁定,它们都将成功锁定。由于您的加载和存储是原子的,因此两个进程都会看到" unlocked"与此同时,两人都获得了锁定。

这意味着您无法执行以下操作:

 if mutex_1 unlocked:
      lock mutex_1

在任意汇编语言中可能看起来像这样:

 load arithmetic mutex_addr
 or arithmetic immediate(1)  // unlocked = 0000 or 1 = 0001, locked = 0001 or 1 = 0001 
 store mutex_addr arithmetic // not putting in conditional label to provide better synchronization. 
 jumpifzero MUTEXLABEL arithmetic 

要解决这个问题,你需要拥有每个"线程"要么知道它当前是否正在锁定其他人,要么完全避免同时锁定访问。

我只看到一种可以在你的系统上完成的方式(通过flag / mutex id检查)。为每个正在检查的互斥锁的每个线程都有一个互斥锁ID,并检查所有其他线程是否可以实际获取锁。你的二进制信号量在这里真的没有帮助,因为如果你打算使用它,你需要将一个互斥量与一个信号量相关联(并且仍然需要从ram加载互斥量)。

检查每个线程解锁和锁定的简单实现,基本上每个互斥锁都有一个ID和一个状态,为了避免每条指令的竞争条件,正在处理的当前互斥锁在实际获取之前就被识别出来。让"识别你想要使用哪个锁"并且"实际上试图获得锁定#34;分两步停止意外获取同时访问。使用此方法,您可以使用2 ^ 16-1(因为0用于表示未找到锁定)互斥锁和您的"线程"可以存在于任何指令管道上。

// init to zero
volatile uint16_t CURRENT_LOCK_ATTEMPT[NUM_THREADS]{0};
// make thread id associated with priority
bool tryAcqureLock(uint16_t mutex_id, bool& mutex_lock_state){
    if(mutex_lock_state == false){
        // do not actually attempt to take the lock until checked everything.  
        // No race condition can happen now, you won't have actually set the lock
        // if two attempt to acquire the same lock at the same time, you'll both 
        // be able to see some one else is as well. 
        CURRENT_LOCK_ATTEMPT[MY_THREAD_ID] = mutex_id;
        //checking all lower threads, need some sort of priority 
        //predetermined to figure out locking. 
        for( int i = 0; i < MY_THREAD_ID; i++ ){
            if((CURRENT_LOCK_ATTEMPT[i] == mutex_id){
                //clearing bit. 
                CURRENT_LOCK_ATTEMPT[MY_THREAD_ID] = 0;
                return false;
            }
        }
        // make sure to lock mutex before clearing which mutex you are currently handling
        mutex_lock_state = true;
        CURRENT_LOCK_ATTEMPT[MY_THREAD_ID] = 0;
        return true;
    }
    return false;
}

// its your fault if you didn't make sure you owned the lock in the first place
// if you did own it, theres no race condition, because of atomic store load. 
// if you happen to set the state while another instruction is attempting to 
// acquire the lock they simply wont get the lock and no race condition occurs
bool unlock(bool& mutex_lock_state){
    mutex_lock_state = false;
}

如果您想要更平等的资源访问权限,您可以更改索引而不是基于i = 0i < MY_THREAD_ID,您可以随机选择一个&#34;起点&#34;使用模运算绕回MY_THREAD_ID。 IE:

bool tryAcqureLock(uint16_t mutex_id, bool& mutex_lock_state, uint16_t per_mutex_random_seed){
    if(mutex_lock_state == false){

        CURRENT_LOCK_ATTEMPT[MY_THREAD_ID] = mutex_id;    
        //need a per thread linear congruence generator for speed and consistency
        std::minstd_rand0 random(per_mutex_random_seed)
        for(int i = random() % TOTAL_NUM_THREADS; i != MY_THREAD_ID i = (i + 1) % TOTAL_NUM_THREADS)
        {
        //same as before
        }
        // if we actually acquired the lock
        GLOBAL_SEED = global_random() // use another generator to set the next seed to be used
        mutex_lock_state = true;
        CURRENT_LOCK_ATTEMPT[MY_THREAD_ID] = 0;
        return true;
    }
    return false;
}

一般来说,缺乏测试和设置能力确实会对所有内容产生影响,这意味着您不得不使用其他算法进行互斥。有关可用于非测试和集体系结构的其他算法的更多信息,请查看this SO post,以及这些仅依赖于原子载荷和存储的维基百科算法:

所有这些算法基本上分解为检查一组标志,以查看是否可以通过遍历每个elses标志来安全地访问资源。