简体   繁体   中英

How to modify structure elements atomically without using locks in C?

I would like to modify some elements of a structure atomically. My current implementation uses mutexes to protect the critical code, and can be seen below.

#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;
}

I am planning to port this code into an RTOS which does not support posix, and I would like to do this operation atomically without using mutexes or disabling/enabling interrupts.

How can I do this operation? Is it possible by using 'atomic compare and swap function' (CAS)?

Seems like in your example you have two threads servicing to different devices. You maybe able to do away with locking completely using a per-device structure. The global will be the aggregate of all per-device statistics. If you do need locks you can use CAS, LL/SC or any supported underlying atomic construct.

What i do is create a union with all the fields I want to change at the same time. like this:

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;

You embed unions like that inside your larger struct like this:

struct {
  ...
  volatile TData m_Data;
  ...
} TBiggerStruct;

Then i do something like this:

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
}

I do a lot of packing of fields that I want to change at the same time into the smallest possible 16, 32, or 64 bit structure. I think 128 bit stuff on intel is not as fast as the 64 bit stuff, so I avoid it. I haven't benchmarked it in awhile so I could be wrong on that.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM