一种方式 delphi 线程通信?
One way delphi thread communication?
考虑我的 Delphi 程序有两个线程。
当然是主要线程和一个运行线程(始终运行它永远不会停止)。
我想知道在运行线程中更新变量(16 个原子整数)而不产生问题的最佳方法是什么。
运行器线程总是通过 USB 发送低级数据(时间要求 <10 毫秒)。
我只需要一种方法,因为运行线程从不向主线程报告。
有什么想法吗?
谢谢!
如果您正在考虑公开 运行 线程的一些变量,然后用您的主线程更改这些变量,请不要这样做!我是认真的。不要!
为什么不呢?因为您的主线程永远不知道何时更改这些变量是安全的,并且最终可能会在运行线程读取它们时同时更改它们。
所以我会去创建一个包含所有变量的记录,但该记录不在您的 runners 范围内。
然后我将确保对该记录中的数据所做的所有更改都受到关键部分的保护。这是为了确保只有一个线程同时访问该记录。
然后在您的运行线程中,我将从该记录中读取数据并制作一个本地副本(这个在您的 运行 线程中)。现在读取记录再次受到临界区的保护。
现在您可能会问自己为什么要从 运行 线程读取这些变量的数据。这样做的主要原因是您的 运行 线程是现在可以更新这些变量中的数据的线程。
我将变量数据存储在记录中的原因是使临界区的进入和离开尽可能快。
您会看到,当您使用临界区时,进入临界区将锁定您将在临界区内访问的所有变量。所以如果你有多个这样的过程可能需要一段时间,因为变量被一个一个地锁定。但是,如果您将所有变量都存储在一条记录中,进入临界区将立即锁定整个记录。
同样的事情适用于离开关键部分并且正在解锁对变量的访问。
你的问题很宽泛。
有多种选择,最适合您的取决于您的具体需求和环境。您需要提供有关您希望主线程在 运行ner 线程上更新的数据类型的更多详细信息。预计多久更新一次?您已经声明 运行ner 线程是时间关键的 - 这意味着您想要保持锁定(PS:多线程速度下降的主要原因多线程应用程序是指不同的线程竞争相同的锁。称为锁争用。)到最低限度。 (正如我所说:这取决于您的需求。)
PS: 必须使用主线程设置的所有数据吗?或者 运行ner 线程仅使用最新的可用数据而忽略可能已分配的许多中间值是否可以接受? 这个问题的答案产生了根本不同的选择。
例如,可以在没有任何锁定的情况下执行原子更新。看看TThread.Terminated
。这是一个简单的值。您不需要任何锁来更新这个特定值。没有问题的竞争条件,因为处理器将读取或写入整个原子单元的值。
即使您同时更新,您的 while not Terminated
循环也在读取值 - 不会有问题。更新要么发生在读取之前(导致线程退出循环),要么发生在读取之后(导致在下一次读取循环退出之前再进行一次迭代)。
PS:重要的是要知道设置字符串值是不是原子操作。
现在您已经指出主线程根本不需要读取 运行ner 线程的数据。但我将使用这种可能性作为对比示例。如果您需要在 运行ner 线程上增加一个整数值,那么 将需要 保护。这是因为递增一个值是一个多步骤操作:
- 读取值。
- 对读取的值执行计算。
- 写一个新值。
如果 运行主线程同时使用该值,您可能会得到不一致的结果。
另一种可能导致问题的情况是数据由许多相互作用的值组成。例如。 NoOfUnits
和MassPerUnit
组合使用,确定TotalMass
。单独更新这些值可能会导致竞争条件,导致行为不一致。
答案提供了一种保护多个值的技术。尽管请注意该答案的当前版本中存在一些严重错误。
请注意,如果您将数据封装在一个单独的对象中,则可以在没有任何锁的情况下更新您的 运行ner 线程的数据,因为您可以自动更新指针值. (非常注意:您必须遵循许多特殊规则,并且您需要弄清楚如何避免内存泄漏......但这是更多细节具体问题。)
另一种选择是将您的 运行ner 线程实现为消息队列。 IE。当主线程想要更改值时,它会向 运行ner 线程发送一条消息。 运行ner 只会在 "safe" 需要时处理更改值指令。 (同样,这的可行性取决于您的具体要求。)
最后要注意的是,除了保护数据不受竞争条件影响外,还有一些其他问题。 您的 运行 线程 的时间紧迫性究竟如何?它做了多少处理?是否需要对某些事件快速响应?如果有,是什么事件?
这些问题的答案对于理解 运行ner 线程主循环的理想结构很重要。例如,"busy loop"(一个循环可能不做任何事情只是为了确保它永远不会暂停而迭代)会使线程高度响应,但会耗尽机器的资源并使其整体变慢。相比之下,消息队列通常会 运行 循环处理消息,直到还剩 none,然后将线程放入 "wait-state",直到收到下一条消息。
PS:内存管理器是争用和速度下降的另一个潜在来源。如果您的主线程和 运行ner 线程都执行大量堆 allocations/deallocations,您可能会在甚至没有显式编码的区域发生锁争用。
考虑我的 Delphi 程序有两个线程。
当然是主要线程和一个运行线程(始终运行它永远不会停止)。
我想知道在运行线程中更新变量(16 个原子整数)而不产生问题的最佳方法是什么。
运行器线程总是通过 USB 发送低级数据(时间要求 <10 毫秒)。
我只需要一种方法,因为运行线程从不向主线程报告。
有什么想法吗?
谢谢!
如果您正在考虑公开 运行 线程的一些变量,然后用您的主线程更改这些变量,请不要这样做!我是认真的。不要!
为什么不呢?因为您的主线程永远不知道何时更改这些变量是安全的,并且最终可能会在运行线程读取它们时同时更改它们。
所以我会去创建一个包含所有变量的记录,但该记录不在您的 runners 范围内。
然后我将确保对该记录中的数据所做的所有更改都受到关键部分的保护。这是为了确保只有一个线程同时访问该记录。
然后在您的运行线程中,我将从该记录中读取数据并制作一个本地副本(这个在您的 运行 线程中)。现在读取记录再次受到临界区的保护。
现在您可能会问自己为什么要从 运行 线程读取这些变量的数据。这样做的主要原因是您的 运行 线程是现在可以更新这些变量中的数据的线程。
我将变量数据存储在记录中的原因是使临界区的进入和离开尽可能快。
您会看到,当您使用临界区时,进入临界区将锁定您将在临界区内访问的所有变量。所以如果你有多个这样的过程可能需要一段时间,因为变量被一个一个地锁定。但是,如果您将所有变量都存储在一条记录中,进入临界区将立即锁定整个记录。
同样的事情适用于离开关键部分并且正在解锁对变量的访问。
你的问题很宽泛。
有多种选择,最适合您的取决于您的具体需求和环境。您需要提供有关您希望主线程在 运行ner 线程上更新的数据类型的更多详细信息。预计多久更新一次?您已经声明 运行ner 线程是时间关键的 - 这意味着您想要保持锁定(PS:多线程速度下降的主要原因多线程应用程序是指不同的线程竞争相同的锁。称为锁争用。)到最低限度。 (正如我所说:这取决于您的需求。)
PS: 必须使用主线程设置的所有数据吗?或者 运行ner 线程仅使用最新的可用数据而忽略可能已分配的许多中间值是否可以接受? 这个问题的答案产生了根本不同的选择。
例如,可以在没有任何锁定的情况下执行原子更新。看看TThread.Terminated
。这是一个简单的值。您不需要任何锁来更新这个特定值。没有问题的竞争条件,因为处理器将读取或写入整个原子单元的值。
即使您同时更新,您的 while not Terminated
循环也在读取值 - 不会有问题。更新要么发生在读取之前(导致线程退出循环),要么发生在读取之后(导致在下一次读取循环退出之前再进行一次迭代)。
PS:重要的是要知道设置字符串值是不是原子操作。
现在您已经指出主线程根本不需要读取 运行ner 线程的数据。但我将使用这种可能性作为对比示例。如果您需要在 运行ner 线程上增加一个整数值,那么 将需要 保护。这是因为递增一个值是一个多步骤操作:
- 读取值。
- 对读取的值执行计算。
- 写一个新值。
如果 运行主线程同时使用该值,您可能会得到不一致的结果。
另一种可能导致问题的情况是数据由许多相互作用的值组成。例如。 NoOfUnits
和MassPerUnit
组合使用,确定TotalMass
。单独更新这些值可能会导致竞争条件,导致行为不一致。
请注意,如果您将数据封装在一个单独的对象中,则可以在没有任何锁的情况下更新您的 运行ner 线程的数据,因为您可以自动更新指针值. (非常注意:您必须遵循许多特殊规则,并且您需要弄清楚如何避免内存泄漏......但这是更多细节具体问题。)
另一种选择是将您的 运行ner 线程实现为消息队列。 IE。当主线程想要更改值时,它会向 运行ner 线程发送一条消息。 运行ner 只会在 "safe" 需要时处理更改值指令。 (同样,这的可行性取决于您的具体要求。)
最后要注意的是,除了保护数据不受竞争条件影响外,还有一些其他问题。 您的 运行 线程 的时间紧迫性究竟如何?它做了多少处理?是否需要对某些事件快速响应?如果有,是什么事件?
这些问题的答案对于理解 运行ner 线程主循环的理想结构很重要。例如,"busy loop"(一个循环可能不做任何事情只是为了确保它永远不会暂停而迭代)会使线程高度响应,但会耗尽机器的资源并使其整体变慢。相比之下,消息队列通常会 运行 循环处理消息,直到还剩 none,然后将线程放入 "wait-state",直到收到下一条消息。
PS:内存管理器是争用和速度下降的另一个潜在来源。如果您的主线程和 运行ner 线程都执行大量堆 allocations/deallocations,您可能会在甚至没有显式编码的区域发生锁争用。