如何在不使用 C 中的锁的情况下原子地修改结构元素?

Posted

技术标签:

【中文标题】如何在不使用 C 中的锁的情况下原子地修改结构元素?【英文标题】:How to modify structure elements atomically without using locks in C? 【发布时间】:2013-09-12 20:40:16 【问题描述】:

我想原子地修改结构的一些元素。 我当前的实现使用互斥锁来保护关键代码,如下所示。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <pthread.h>

pthread_mutex_t thread_mutex = PTHREAD_MUTEX_INITIALIZER;

#define ITER    100000

typedef struct global_status 
    int32_t context_delta;
    uint32_t global_access_count;
 global_status_t;

global_status_t g_status;

void *context0(void *ptr)

    unsigned int iter = ITER;
    while (iter--) 
        wait_event_from_device0();
        pthread_mutex_lock(&thread_mutex);
        g_status.context_delta++;
        g_status.global_access_count++;
        pthread_mutex_unlock(&thread_mutex);
    

    return NULL;


void *context1(void *ptr)

    unsigned int iter = ITER;
    while (iter--) 
        wait_event_from_device1();
        pthread_mutex_lock(&thread_mutex);
        g_status.context_delta--;
        g_status.global_access_count++;
        pthread_mutex_unlock(&thread_mutex);
    

    return NULL;


int main(int argc, char **argv)

    pthread_t tid0, tid1;
    int iret;

    if ((iret = pthread_create(&tid0, NULL, context0, NULL))) 
         fprintf(stderr, "context0 creation error!\n");
         return EXIT_FAILURE;
    

    if ((iret = pthread_create(&tid1, NULL, context1, NULL))) 
         fprintf(stderr, "context1 creation error!\n");
         return EXIT_FAILURE;
    

    pthread_join(tid0, NULL);
    pthread_join(tid1, NULL);

    printf("%d, %d\n", g_status.context_delta, g_status.global_access_count);
    return 0;

我计划将此代码移植到不支持 posix 的 RTOS 中,并且我希望在不使用互斥锁或禁用/启用中断的情况下自动执行此操作。

我该如何做这个操作? 是否可以使用“原子比较和交换功能”(CAS)?

【问题讨论】:

您的代码需要经常更新吗?您如何将程序的读取更新比率置于实际工作负载中? @darnir 我无法回答您的问题。你能详细说明一下吗?而且我也没有机会测量实际工作量,因为我无法移植到真实设备中。 您认为需要多久更新一次结构?你会轮询一些传感器并不断更新结构吗?还是很少需要更新结构? @darnir 他们很频繁。 device0 绑定到一个由定时器驱动的外设(大约 10kHz 执行周期),而 device1 由通信设备驱动,其执行周期比 device1 快。所以他们的速度足以制造问题。 好的。我打算建议您使用用户空间 RCU 实现。但如果数据结构的更新不频繁,它只是一种很好的同步机制。 【参考方案1】:

在您的示例中,您似乎有两个线程为不同的设备提供服务。您也许可以使用每个设备的结构完全取消锁定。全局将是所有每台设备统计信息的汇总。如果确实需要锁,可以使用 CAS、LL/SC 或任何受支持的底层原子构造。

【讨论】:

CAS 和 LL/SC 不使用锁【参考方案2】:

我所做的是创建一个包含我想同时更改的所有字段的联合。像这样:

union 
  struct 
    int            m_field1;
    unsigned short m_field2 : 2,
                   m_field3 : 1;
    BYTE           m_field4; 
  
  unsigned long long m_n64;
  TData(const TData& r)  m_n64 = r.m_n64; 
 TData;

您可以像这样在较大的结构中嵌入这样的联合:

struct 
  ...
  volatile TData m_Data;
  ...
 TBiggerStruct;

然后我做这样的事情:

while (1) 
  TData Old = BiggerSharedStruct.m_Data, New = Old;
  New.field1++;
  New.field4--;
  if (CAS(&SharedData.m_n64, Old.m_n64, New.m_n64))
    break; // success

我将很多字段打包,同时希望将其更改为尽可能小的 16、32 或 64 位结构。我认为 intel 上的 128 位的东西不如 64 位的东西快,所以我避免使用它。我有一段时间没有对其进行基准测试,所以我可能错了。

【讨论】:

以上是关于如何在不使用 C 中的锁的情况下原子地修改结构元素?的主要内容,如果未能解决你的问题,请参考以下文章

Java并发入门之互斥锁-解决原子性问题

内核中的锁机制--RCU

Oracle中的锁

Oracle中的锁

CAS 与原子操作

互斥锁,自旋锁,原子操作原理和实现