【问题标题】:C reader and writer threadsC 读写器线程
【发布时间】:2014-06-22 08:13:41
【问题描述】:

我正在编写具有以下结构的 C 多线程程序:

struct mystruct {
    int a;
    int b;
    int c;
    int d;
} Data;


void *thr_1();
void *thr_2();


int main(int argc, char *argv[]) {
    pthread_t t_1,
          t_2;

    if (pthread_create(&t_1, NULL, thr_1, NULL)
     || pthread_create(&t_2, NULL, thr_2, NULL)) {
        perror("pthread_create() on main()");
        return -1;
    }

    while (a < 2000) {
    /* do a lot of stuff with Data (never writes on it) */
    }

    pthread_cancel(thr_1);
    pthread_cancel(thr_2);
    pthread_join(thr_1, NULL);
    pthread_join(thr_2, NULL);

    return 0;
}

/* Threads */
void *thr_1() {
    while (1) {
        /* Read from stream and write to Data */
        usleep(50000);
    }
    return NULL;
}

void *thr_2() {
    while (1) {
        /* do some stuff with Data (never writes on it) */
        usleep(50000);
    }
    return NULL;
}

这段代码可以运行,但经过一些研究(竞争条件和线程安全)后,很明显这段代码可能会因为这些竞争条件而随时失败。我当时的第一个想法是使用互斥锁在写入时锁定结构的成员并在之后释放它,但是在主循环和 thr_2 中对 Data 结构的读取访问过多。到目前为止,我的解决方案是创建 2 个访问函数,一个读取数据,一个写入数据,在这些函数内部,使用互斥锁来锁定写入。这个丑陋的解决方案对我来说更像是一个黑客......所以,最后,我的问题是:有没有更好的方法来做到这一点?最好没有访问数据所需的任何功能。

非常感谢!

【问题讨论】:

  • 你不喜欢互斥锁?
  • 我很喜欢...但是读取的数据太多...并且对于每次读取我需要先锁定然后解锁...必须有更好的方法...。

标签: c multithreading thread-safety pthreads


【解决方案1】:

您确实需要更多信息才能做出真正明智的回应,但需要一些快速的项目。

Atomics 也可以在 boost 中使用,boost::atomic<>,这将允许您执行以下操作。

struct mystruct 
{
    boost::atomic<int> a;
    boost::atomic<int> b;
    boost::atomic<int> c;
    boost::atomic<int> d;
}Data;

boost 应该根据平台为您解决所有问题,因为有些问题没有完全限定的 CAS 和/或内存屏障。

沿着这些思路,您还可以在 boost.js 中使用lockfree 结构之一。请记住,在这两种情况下,atmoics 和 lockfree,最好的情况是它们是用 CAS 操作实现的,最坏的情况可能更丑陋。甚至 CAS 也不是免费的,因为该操作可能会清空 CPU 的指令缓存,从而导致执行延迟。

但是,根据您的用例(正如您所指出的,只有一个线程写入数据),如果您的处理可以容忍数据的延迟更新,则不需要同步。

【讨论】:

    【解决方案2】:

    C11 支持原子操作。不幸的是,大多数编译器不支持 C11 原子,但您可以使用 GCC 4.9 来支持它们:

    #include <stdatomic.h>
    
    struct mystruct
    {
        _Atomic int a;
        ...
    };
    
    int load(struct mystruct* s)
    {
        return atomic_load(&s->a);
    }
    
    void store(struct mystruct* s, int value)
    {
        atomic_store(&s->a, value);
    }
    

    请注意,仅保护写操作是不够的。 C11 标准对此非常清楚。如果两个线程可以同时访问同一个内存位置,并且至少有一个操作是写访问,那么程序的行为是不确定的。这意味着您必须保护所有访问。

    【讨论】:

    • 这是一个很好的答案,但我忘了提到我已经找到了这个解决方案,我可能无法使用 gcc 4.9 =(... 只是为了好奇,你为什么需要 atomic_load 和 atomic_store ? 你不能就 s->a = 10 吗??如果可以的话,我会投票赞成....
    • 原来我可以使用gcc 4.9...再次感谢您的帮助!仍然想知道为什么我必须使用 atomic_load 和 atomic_store ;D...
    • 我知道你必须使用atomic_loadatomic_store,但我不能告诉你为什么。我想其目的是能够主要将 atomics 实现为库而不是编译器功能。如果您使用的是 C++,则可以改用 std::atomic&lt;int&gt;。后者重载操作,因此您无需显式调用函数即可对其进行读写。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-19
    • 1970-01-01
    • 1970-01-01
    • 2021-08-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多