C++互斥锁和RTOS xMutex的区别

5

我正在尝试在ESP32上进行锁定实验。显然,有不同的方法来实现锁定:

  1. There is the default C++ mutex library:

    #include <mutex>
    
    std::mutex mtx;
    
    mtx.lock();
    
    mtx.unlock();
    
  2. And there is the implementation from RTOS:

    SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();
    
    xSemaphoreTake(xMutex, portMAX_DELAY);
    
    xSemaphoreGive(xMutex);
    

我需要知道是否存在根本性的差异?还是它们是等效的?


1
不查看您系统上标准 C++ 库的代码,无法确定其实现方式 - C++ 标准仅指定程序如何使用,而非其实现方式。两者之间的主要实际区别在于,第一个可以在所有 C++11 及更高版本的实现(主机系统、编译器和标准库的组合)中工作,除非实现存在缺陷,而第二个只能在特定的实现中工作,而其他实现则不能。 - Peter
这些不是实现锁的不同方式,而是不同的库API。实现在库例程中,由您的代码调用。 - Solomon Slow
1
你是在使用ESP-IDF SDK还是其他什么东西? - rustyx
@rustyx 不一定。我也在使用Arduino核心 - Falko
2个回答

4
假设您正在使用ESP-IDF SDK,工具链基于GCC 5.2,针对xtensa-lx106指令集,带有部分开源C运行时库。GNU libstdc++中的std::mutex委托给pthread_mutex_lock/unlock调用。ESP-IDF SDK包含一个pthread仿真层,在那里我们可以看到pthread_mutex_lockpthread_mutex_unlock实际上是做什么的:
static int IRAM_ATTR pthread_mutex_lock_internal(esp_pthread_mutex_t *mux, TickType_t tmo)
{
    if (!mux) {
        return EINVAL;
    }

    if ((mux->type == PTHREAD_MUTEX_ERRORCHECK) &&
        (xSemaphoreGetMutexHolder(mux->sem) == xTaskGetCurrentTaskHandle())) {
        return EDEADLK;
    }

    if (mux->type == PTHREAD_MUTEX_RECURSIVE) {
        if (xSemaphoreTakeRecursive(mux->sem, tmo) != pdTRUE) {
            return EBUSY;
        }
    } else {
        if (xSemaphoreTake(mux->sem, tmo) != pdTRUE) {
            return EBUSY;
        }
    }

    return 0;
}

int IRAM_ATTR pthread_mutex_unlock(pthread_mutex_t *mutex)
{
    esp_pthread_mutex_t *mux;

    if (!mutex) {
        return EINVAL;
    }
    mux = (esp_pthread_mutex_t *)*mutex;
    if (!mux) {
        return EINVAL;
    }

    if (((mux->type == PTHREAD_MUTEX_RECURSIVE) ||
        (mux->type == PTHREAD_MUTEX_ERRORCHECK)) &&
        (xSemaphoreGetMutexHolder(mux->sem) != xTaskGetCurrentTaskHandle())) {
        return EPERM;
    }

    int ret;
    if (mux->type == PTHREAD_MUTEX_RECURSIVE) {
        ret = xSemaphoreGiveRecursive(mux->sem);
    } else {
        ret = xSemaphoreGive(mux->sem);
    }
    if (ret != pdTRUE) {
        assert(false && "Failed to unlock mutex!");
    }
    return 0;
}

因此,正如您所看到的,它主要将调用委托给RTOS信号量API,并进行一些额外的检查。
很可能您不需要/不想要这些检查。考虑到esp32芯片的微小i-cache和极其缓慢的串行RAM,我更喜欢尽可能接近硬件(即除非std::mutex恰好符合您的需求,否则不要使用)。

太好了!这正是我在找的。谢谢! :) - Falko
1
非常好的详细解释,但我不会完全同意最终结论:坚持使用更高级别的机制可能会引入开销,但是我们都知道过早优化可能非常危险。因此,只要没有将高级实现标识为瓶颈,我个人建议使用它而不是裸机编程。 - Christian B.

1

我应该注意哪些基本差异?

我不熟悉你在第二个示例中调用的API,但看起来你的xMutex变量指的是一个计数信号量。 "信号量"抽象比"互斥锁"抽象更强大。也就是说,你总是可以使用信号量替代互斥锁,但有一些算法不能用互斥锁替代信号量。

我喜欢将信号量视为一个没有信息的令牌阻塞队列。 "give"操作将一个令牌放入队列中,而"take"从队列中取出一个令牌,如果队列此时为空,则可能等待某个其他线程给出令牌。


注意,如果想要使用信号量代替互斥锁,需要在互斥锁“空闲”时包含一个令牌,并且在互斥锁“正在使用”时不包含任何令牌。这意味着,您希望创建信号量的代码在开始时确保它包含一个令牌。
您示例中的xMutex = xSemaphoreCreateMutex()语句没有明确显示新信号量包含多少令牌。如果它是零个令牌,则您可能希望在下一行代码中“give()”一个令牌以完成初始化。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接