C - 互斥锁的 getter 和 setter 最佳实践



在嵌入式编程中使用多个线程时,我感到有点不知所措,因为每个共享资源最终都有一个受互斥锁保护的 getter/setter。

我真的很想了解以下类型的 getter 是否

static float static_raw;
float get_raw() {
os_mutex_get(mutex, OS_WAIT_FOREVER);
float local_raw = static_raw;
os_mutex_put(mutex);
return local_raw ;
}

有意义,或者如果float赋值可以被认为是原子的,例如对于 ARM(不同于例如 64 位变量(,则使其变得多余。

我可以理解这样的事情:

raw = raw > VALUE ? raw + compensation() : raw;

多次处理值,但在读取或返回它时呢?

你能把我的想法说清楚吗?

编辑 1: 关于下面的第二个问题。 假设我们在时间执行方面有一个"重"函数,我们称之为

void foo(int a, int b, int c)

其中 a、b、c 可能是来自共享资源的值。 当调用 foo 函数时,它是否应该被互斥锁包裹,即使它只需要一个值的副本,也要锁定它很长时间?例如

os_mutex_get(mutex, OS_WAIT_FOREVER);
foo(a,b,c);
os_mutex_put(mutex);

这样做有意义吗

os_mutex_get(mutex, OS_WAIT_FOREVER);
int la = a;
int lb = b;
int lc = c;
os_mutex_put(mutex);
foo(la,lb,lc);

仅锁定变量的副本而不是完全执行?

编辑2: 鉴于可能存在"a"、"b"和"c"的获取者和设置器。 在性能方面/或其他任何事情方面做这样的事情有问题吗?

int static_a;
int get_a(int* la){
os_mutex_get(mutex, OS_WAIT_FOREVER);
*la = static_a;
os_mutex_put(mutex);
}

int static_b;
int get_b(){
os_mutex_get(mutex, OS_WAIT_FOREVER);
int lb = static_b;
os_mutex_put(mutex);
return lb;
}

将它们用作

void main(){
int la = 0;
get_a(&la);
foo(la,get_b());
}

我问这个是因为 im 无缘无故地按顺序锁定和重新锁定同一个互斥锁。

如果浮点赋值可以被认为是原子的

在 C 语言中,除非使用 C11_Atomic或内联汇编程序,否则任何内容都不能被视为原子。底层硬件是无关紧要的,因为即使可以在给定硬件上的单个指令中读取特定大小的单词,也永远无法保证某个 C 指令只会产生单个指令。

这样做有意义吗

os_mutex_get(mutex, OS_WAIT_FOREVER);
int la = a;
int lb = b;
int lc = c;
os_mutex_put(mutex);
foo(a,b,c);

假设你的意思是foo(la,lb,lc);,那么是的,这很有意义。这就是理想情况下应该如何使用互斥锁:最小化互斥锁之间的代码,使其只是原始变量复制,而不是其他内容。

C 标准没有规定任何关于赋值运算符的原子性。您不能考虑赋值原子,因为它完全依赖于实现。

但是,在 C11 中,可以使用_Atomic类型限定符(C11 §6.7.3,此处第 121 页(来声明要以原子方式读取和写入的变量,因此您可以执行以下操作:

static _Atomic float static_raw;
float get_raw(void) {
return static_raw;
}

如果这样做,请不要忘记使用-std=c11进行编译。


解决您的第一次编辑:

当调用 foo 函数时,它是否应该被互斥锁包裹,即使它只需要一个值的副本,也要锁定它很长时间?

虽然这是正确的,但它肯定不是最好的解决方案。如果函数只需要变量的副本,那么您的第二个代码段无疑要好得多,并且应该是理想的解决方案:

os_mutex_get(mutex, OS_WAIT_FOREVER);
int la = a;
int lb = b;
int lc = c;
os_mutex_put(mutex);
foo(la,lb,lc);

如果你锁定了整个函数,你将阻止任何其他线程试图获取锁的时间比需要的时间长得多,从而减慢一切。在调用函数之前锁定并传递值的副本将只锁定所需的时间,而将更多的空闲时间留给其他线程。


解决您的第二次编辑问题:

鉴于可能存在"a"、"b"和"c"的获取者和设置器。在性能方面/或其他任何事情方面做这样的事情有问题吗?

该代码是正确的。就性能而言,如果可以的话,每个变量有一个互斥锁肯定会好得多。只有一个互斥锁时,任何持有互斥锁的线程都将"阻止"任何其他试图锁定它的线程,即使它们试图访问不同的变量。

如果您不能使用多个互斥锁,则需要在这两个选项之间进行选择:

  1. 锁定吸气器内部:

    void get_a(int* la){
    os_mutex_get(mutex, OS_WAIT_FOREVER);
    *la = static_a;
    os_mutex_put(mutex);
    }
    void get_b(int* lb){
    os_mutex_get(mutex, OS_WAIT_FOREVER);
    *lb = static_b;
    os_mutex_put(mutex);
    }
    /* ... */
    int var1, var2;
    get_a(&var1);
    get_b(&var2);
    
  2. 锁定在 getter 之外(将职责留给调用者(:

    int get_a(void){
    return static_a;
    }
    int get_b(void){
    return static_b;
    }
    /* ... */
    os_mutex_get(mutex, OS_WAIT_FOREVER);
    int var1 = get_a();
    int var2 = get_b();
    os_mutex_put(mutex);
    

    在这一点上,你甚至不需要有getter,你可以这样做:

    os_mutex_get(mutex, OS_WAIT_FOREVER);
    int var1 = a;
    int var2 = b;
    os_mutex_put(mutex);
    

如果您的代码经常请求多个值,那么在 getter之外锁定/解锁会更好,因为它会导致更少的开销。作为替代方案,您也可以将锁定保留在内部,但创建不同的函数来检索多个变量,以便互斥锁仅锁定和释放一次。

另一方面,如果您的代码很少请求多个值,那么在每个 getter保持锁定是可以的。

无法提前说明最佳解决方案是什么,您应该运行不同的测试,看看什么最适合你的方案。

最新更新