当前位置: C语言 -- 基础 -- 多线程执行和数据竞争

多线程执行和数据竞争(三)


2、获取操作 释放操作和消耗操作

一个或者多个内存位置上的同步操作可能是获取操作(acquire operation)、释放操作(release operation)、获取/释放操作(acquire and release operation)或者消耗操作(consume operation),例如:加锁互斥是获取操作,解锁互斥是释放操作。

对互斥执行释放操作会迫使其它内存位置上的先前副作用对后来对同一互斥执行获取或者消耗操作的其它线程可见。

int i1 = 0;
int i2 = 0;
mtx_t mutex;

/*线程一。*/
...         //其它操作。
mtx_lock(&mutex);
i1 = 5;
mtx_unlock(&mutex);
printf("i2 = %d\n", i2);
...         //其它操作。    

/*线程二。*/
...         //其它操作。
mtx_lock(&mutex);
i2 = 3;
mtx_unlock(&mutex);
printf("i1 = %d\n", i1);
...         //其它操作。

如果线程一先加锁互斥,线程一中赋值i1 = 5在线程二加锁互斥后是可见的,这种情况下线程二中i1值为5;如果线程二先加锁互斥,线程二中赋值i2 = 3在线程一加锁互斥后是可见的,这种情况下线程一中i2值为3


栅栏是无关联内存位置的同步操作。栅栏可能是获取栅栏(acquire fence)、释放栅栏(release fence),也可能是获取/释放栅栏(acquire and release fence)。

atomic_int M = 0;

/*线程一。*/
...         //其它操作。
atomic_thread_fence(memory_order_release);              //这是一个释放栅栏。
atomic_store_explicit(&M, 10, memory_order_relaxed);    

/*线程二。*/
while(!atomic_load_explicit(&M, memory_order_relaxed))  
    ;
atomic_thread_fence(memory_order_acquire);              //这是一个获取栅栏。
...         //其它操作。

松散的原子操作(relaxed atomic operations)不是同步操作,但和同步操作一样,松散的原子操作不会导致数据竞争。松散的原子操作具有原子的读-修改-写操作特性。每个原子对象都有一个单独的顺序;但不需要将所有原子对象组合成单个的总序。这通常是不可能的,因为对于不同变量的修改,不同线程可能观察到不一致的顺序。


以对原子对象M执行释放操作A为首的释放序列(release sequence)是原子对象M修改顺序中副作用的最大连续子序列,其中第一个操作是释放操作A,并且后续每个操作要么由执行释放操作的同一线程执行,要么是原子的读-修改-写操作。

对原子对象M执行释放操作的原子操作A与对原子对象M执行获取操作的原子操作B同步,原子操作B读取以释放操作A为首的释放序列中的任何副作用写入的值。

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
/*有关释放序列的范例*/

#ifdef __STDC_NO_ATOMICS__
#error "Implementation does not support atomic types."
#endif

#ifdef __STDC_NO_THREADS__
#error "Implementation does not support multi-threads."
#endif

#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <threads.h>

atomic_int aNumber = 0;
struct timespec interval = {0,20};

/*新线程中执行的函数。*/
int func1(void *arg)
{
    atomic_store_explicit(&aNumber, 25, memory_order_release);	//X
    thrd_exit(0);
}

int func2(void *arg)
{
    thrd_sleep(&interval, 0);
    atomic_fetch_add(&aNumber, 5);	//Y
    thrd_exit(0);
}

int main(void)
{
    thrd_t threadId1, threadId2;

    /*创建新线程。*/
    if(thrd_create(&threadId1, func1, NULL) != thrd_success)
        exit(EXIT_FAILURE);

    if(thrd_create(&threadId2, func2, NULL) != thrd_success)
        exit(EXIT_FAILURE);

    /*连接新线程。*/
    thrd_join(threadId1, NULL);
    thrd_join(threadId2, NULL);

    printf("%d\n", atomic_load_explicit(&aNumber, memory_order_acquire));	//Z
    
    return 0;
}

X处的释放操作和Y处原子的读-修改-写操作构成了一个释放序列,Z处的获取操作读取了该释放序列写入的值。