如何防止get()和set()之间的竞争条件行为?

时间:2018-01-18 14:26:08

标签: c synchronization race-condition

考虑以下全局变量foo及其get()set()函数:

int32_t foo;
mutexType fooMutex;

int32_t getfoo(void)
{
    int32_t aux;
    MutexLock(fooMutex);
    aux = foo;
    MutexUnlock(fooMutex);
    return aux;
}
void setfoo(int32_t value)
{
    MutexLock(fooMutex);
    foo = value;
    MutexUnlock(fooMutex);
}

以下任务修改foo:

void ResetTask()
{
    while(1)
    {
        setfoo(resetvalue);
        AccessPeripheral2();
        wait(resetPeriod);
    }
}

void ActTask()
{
    while(1)
    {
        foocopy = getfoo();
        if (foocopy > 0)
        {
            x = AccessPeripheral1();
            setfoo( foocopy - x);
        }
        wait(actPeriod);
    }
}

accessPeripheralX()函数内部有关键部分,但保护不同的资源。

如果实现if条件(foocopy> 0),但是在ActTask()的getfoo()和setfoo()之间重置了foo,那么它将在结束时设置为过时的无效值。循环,产生错误的计算。

如何防止这种竞争状况?
将ActTask()重写为:

if (foocopy > 0)
{
    x = AccessPeripheral1();
    setfoo( getfoo() - x);
}

在实践中消除了它,但理论上,任务仍然可以在获取和设置foo之间被抢占。

我还考虑过在处理foo时引入另一个关键部分:

[Enter critical section]
foocopy = getfoo();
if (foocopy > 0)
{
    x = AccessPeripheral1();
    setfoo( foocopy - x);
}
[Leave critical section]

[Enter critical section]
setfoo(resetvalue);
AccessPeripheral2();
wait(resetPeriod);
[Leave critical section]

但我倾向于避免这种选择,因为我发现嵌套的关键部分会大大增加复杂性(以及错误的相似性)。

1 个答案:

答案 0 :(得分:2)

您只有一个关键部分(请参见下面的假设),它的范围从读取foo到写入它,因为写入取决于读取的值。不是写值取决于读值,即写的是

只要读取的值不影响写入(值或写入本身),读取本身就没有竞争条件。
例如。读取该值然后将其打印到控制台以供人类思考是无害的。

只要要写入的值不取决于当前值,写入本身就没有竞争条件。例如。将5写入包含4的变量的尝试是无害的。但是,如果想法是在写入之后,变量应该比写入之前高一个,那么您具有竞争条件,可以进行任何其他增量访问。
例如。在4上的两个抢占式增量,如果都没有受到保护,则预期两者都会起作用,即正确的结果应该是6,可以以5结束。

我假设读/写单个变量是原子的。即如果您尝试写入一个给定的值而抢占发生时要写入一个不同的值,则任一值最终都将出现在变量中,而不是两者的混合,例如一个值的高字节,另一个值的低字节。与读取相同,即具有抢占式写入访问权限的读取将产生旧的一致值或新的值。
严格来说,这是不正确的,即不需要标准的编译器来保证这一点。如果您担心这个特殊的原子性问题,那么请将保护保留在getter和setter内,或者使用用于隐式保证这一点的编写机制。

您描述的问题所在的赛车条件始于读取权限

foocopy = getfoo();

因为此处读取的值会影响写访问权限

setfoo( foocopy - x);

这样做的意图是,仅当变量包含的值大于零时才应写入新值,这很明显

if (foocopy > 0)

即真正要保护的关键部分是这个

foocopy = getfoo();
if (foocopy > 0)
{
    x = AccessPeripheral1();
    setfoo( foocopy - x);
}   

当然,倒数第二个代码段中正是您保护的部分。
但是,没有必要保护所有最后的代码片段。在易受攻击的关键部分,仅需阻止写入foo。这不是因为写作本身是一个易受攻击的关键部分。这只是“攻击者”,潜在的问题。必须将其锁定,以防止上述脆弱的关键部分。

将所有内容放在一起,我提出以下建议:

int32_t foo;
mutexType paranoiaMutex;
/* only needed to protect the unrelated critical section of reading or writing,
   separatly, if that is not atomic */

int32_t getfoo(void)
{
    int32_t aux;
    MutexLock(paranoiaMutex);   /* or use atomic mechanism */
    aux = foo;
    MutexUnlock(paranoiaMutex); /* or use atomic mechanism */

    /* note this by the way,  
       you might have overlooked something in your design here */
    return aux; /* not foo */
}

void setfoo(int32_t value)
{
    MutexLock(paranoiaMutex);   /* or use atomic mechanism */
    foo = value;
    MutexUnlock(paranoiaMutex); /* or use atomic mechanism */

    /* Using fooMutex additionally here is imaginable,
       but in order to minimise the confusion of nested mutexes,
       I propose to use that mutex on the same "layer" of coding.
       Note that only one mutex and one layer of mutex nesting
       is occuring inside this code part.

       You ARE right about being careful with that...
     */
}

mutexType fooMutex;
/* Note that only one mutex and one layer of mutex nesting 
   is occuring inside this code part. */

void ResetTask()
{
    while(1)
    {
        MutexLock(fooMutex);
        setfoo(resetvalue);
        MutexUnlock(fooMutex);
        AccessPeripheral2();
        wait(resetPeriod);
    }
}

void ActTask()
{
    while(1)
    {
        MutexLock(fooMutex);
        foocopy = getfoo();
        if (foocopy > 0)
        {
            x = AccessPeripheral1();
            setfoo( foocopy - x);
        }
        MutexUnlock(fooMutex);
        wait(actPeriod);
    }
}

可以对锁定的互斥对象的持续时间进行优化,这是“昂贵的”。
此优化假设

  • AccessPeripheral1()速度慢(大多数外围设备访问速度都很慢)
  • 如果AccessPeripheral1();foo<=0可以(可能是不希望的)
  • 可以早点进行AccessPeripheral1();, 特别是比阅读foo更早(可能不希望如此)

这些假设很重要,需要验证,我当然不能做。
但是,如果这些假设适用,则可以按如下所示进行更改,以缩短关键部分。

void ActTask()
{
    while(1)
    {
        x = AccessPeripheral1();
        MutexLock(fooMutex);
        foocopy = getfoo();
        if (foocopy > 0)
        {                
            setfoo( foocopy - x);
        }
        MutexUnlock(fooMutex);
        wait(actPeriod);
    }
}
相关问题