【问题标题】:Global data being written by one thread and read by multiple threads全局数据单线程写入多线程读取
【发布时间】:2012-04-01 20:14:36
【问题描述】:

只有一个线程和一个线程将写入/修改数据结构。该数据结构有一个字段,它是由写入线程打开的套接字描述符。另外两个线程将读取数据结构并且必须做一些事情(比如轮询存储在结构中的套接字以获取输入数据)。

一个线程将读取由写入线程写入的一个全局结构,而另一个线程将读取第二个全局结构。本质上,两个读取线程访问的不是同一个全局结构。

我的问题是,如果写入线程对数据结构进行更改,可能会在数据结构中添加更多记录或删除它们,进程会崩溃吗?是否有任何可能发生运行时崩溃的实例?

附:我正在使用在 Linux 环境中工作的 POSIX 线程

【问题讨论】:

    标签: c pthreads


    【解决方案1】:

    是否有任何空间可以容纳运行时崩溃的任何类型的实例 发生

    简短回答:很可能是的。您需要使用某种锁定来保护数据结构。想到了 Pthread 互斥锁。

    更长的答案:考虑一个链表。考虑添加一个元素或删除一个元素。通常您需要修改该列表的多个方面,例如您需要修改 2 个指针。现在问问自己:如果发生以下情况会发生什么:

    • 编写器线程开始更改列表的某些方面(例如更新 next 指针),但在被中断之前还没有完成
    • 阅读器线程尝试从列表中读取

    编辑

    正如 Blagovest Buyukliev 在 cmets 中提到的那样,无锁数据结构通常提供更高的性能,因此您可以研究该路线。

    【讨论】:

    • 我想避免使用 pthread 互斥锁。我有一种机制,写入线程会要求读取线程停止轮询要删除的套接字。并且没有修改以前的记录。它是添加或删除记录。
    • @VSN 你提到了 Linux。你知道 linx 上的 pthread 互斥锁是 lightning fast 吗?
    • 我不知道 futex :O 那不会锁定整个数据结构吗?
    • @cnicutar:虽然简单的解决方案是使用锁来保护数据,但更有效的方法是使用允许无锁实现的原子数据类型。这个问题听起来很适合lock-free linked list
    • @BlagovestBuyukliev 当然 :-) 那么为什么不预先做一些琐碎的事情,然后如果在分析之后你决定 pthread_mutex_lock 占用了很多时间切换到无锁结构呢?
    【解决方案2】:

    虽然这取决于数据结构,但对于大多数数据结构来说,答案是:当然。

    考虑一个可变长度的向量作为最简单的例子。编写器线程通过减少元素计数然后进行一些清理来删除元素。阅读器线程遍历所有元素。现在假设读取器线程的循环到达最后一个元素,已将索引与元素计数进行了比较,并且即将访问该元素。上下文切换发生,编写器线程弹出最后一个元素,减少元素计数并清理元素。现在最后一个元素无效,发生上下文切换,读取器访问了一个无效元素。

    【讨论】:

      【解决方案3】:

      听起来您可能对pipes 感兴趣。在写入/读取线程开始之前初始化两个管道,并将写入线程传递给管道的写入端,并将读取线程传递给各自管道的读取端。那就不用担心同步问题了。

      回答有关管道用于进程间通信的问题。我建议使用管道,因为您谈到了使用套接字。用于网络间通信的套接字。这个post 讨论了linux内核上管道和套接字之间的区别。对于本地机器通信,管道在速度和效率方面优于套接字,因为它们的实现比套接字简单得多。最后,您不会遇到管道的线程问题,因为这是由内核为您处理的。

      【讨论】:

      • 两个进程之间没有使用管道还是我在这里遗漏了什么......?
      • 一般来说是的。但是在 Linux 中管道有一个非常有效的实现。我更新了我的答案,并提供了管道和套接字之间简要比较的链接。
      【解决方案4】:

      一般来说,答案是肯定的,您很容易在各处发生崩溃。例如,如果一个线程读取链表的一个元素,而另一个线程将其删除,则会出现未定义的行为(读取:崩溃)。

      最简单的事情是使用互斥锁,但如果您准备使用一些高级技术并访问compare and swap operation,您可以实现lock-free linked list,但您应该首先分析您的应用程序以查看速度改进是值得你努力。

      【讨论】:

        【解决方案5】:

        嗯.. 你也许可以在不加锁的情况下逃脱,但你需要小心 :) 数组/列表/任何容器中的每个元素都可能有一个状态枚举,也许:

        枚举状态 {EstFree,EstInUse,EstDeletePending}

        为了添加一个条目,编写线程迭代数组/列表并使用状态为“EstFree”的第一个条目。如果没有,它会创建另一个元素并将其添加到末尾。

        要“删除”一个“EstInUse”条目,请将其状态设置为“EdeletePending”。当使用该元素的线程注意到状态变化时,它知道停止轮询,因此将状态设置为 'EstFree' 并退出。

        【讨论】:

          【解决方案6】:

          正如其他人所说,您需要避免比赛。如果您不想使用互斥锁来保护您的数据,现代 C、C11 会在语言级别引入 atomic 操作。在直接得到更广泛的支持之前,您可以将compiler extension 用于 gcc 和兼容(clang、icc、opencc)或通过macros that emulate the new interface

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-06-08
            相关资源
            最近更新 更多