从一个线程读取变量并从另一个线程写入的正确方法?

Posted

技术标签:

【中文标题】从一个线程读取变量并从另一个线程写入的正确方法?【英文标题】:Proper way to read variable from one thread and write from another? 【发布时间】:2019-11-02 10:22:38 【问题描述】:

我正在尝试创建一个具有 2 个线程的应用程序。在主线程中我有一个变量(Int32),我只能从主线程中读取。

现在我有另一个线程在循环运行,只要程序正在运行,这个线程就会创建一个“心跳”,当达到某个点时,我想更新上面所说的变量。变量只从这个心跳线程写入,从不从任何其他线程写入。

为了保持精度(QueryPerformanceCounter 级别),我想避免心跳线程上任何可能的阻塞。

这是一个简化版:

using System;
using System.Threading;

namespace ConsoleApp2

    class Program
    
        static int Interval = 0;

        static void Heartbeat()
        
            int counter = 0;
            while (counter < 100) 
                counter++;
                Console.WriteLine($"Heartbeat : counter");
                if (counter % 10 == 0) 
                    Interval = counter;
                
                Thread.Sleep(100);
            
        

        static void Main(string[] args)
        
            Thread heartbeat = new Thread(Heartbeat);
            heartbeat.Start();

            while (Interval < 100) 
                Console.WriteLine($"Interval  : Interval");
                Thread.Sleep(100);
            

            Console.ReadLine();
        
    

它运行,但我想知道我的做法是否正确。如果不是,我应该如何改进它?

总结:

    Interval由主线程读取。 IntervalHeartbeat 线程修改。 我想避免对Heartbeat 线程的任何阻塞,最好不要阻塞主线程。

编辑:

我刚刚意识到我在Heartbeat() 中使用while 块读取Interval,所以我将它从while (Interval &lt; 100) 更改为while (counter &lt; 100)

编辑2:

    上面的代码是一个非常简化的版本,真正的代码有点乱,我认为发布所有的 API 部分对这个问题没有帮助。

    不需要在 Interval 更新后立即执行某些操作。它更有可能在每个循环中只读取一次,如下所示:

int interval;
while (Interval < 100 /* I could use some other condition not invoving Interval */ ) 
    interval = Interval;
    // all usage of "Interval" from this point would use the value read in interval
    Console.WriteLine($"Interval  : interval");
    Thread.Sleep(100);

    只要在主线程中读取的值是合法值(在Heartbeat 线程中计算)而不是其他值,我就可以接受。例如,如果值在我为这个循环读取它的同时更新,那么获取旧值或新值是可以的,只要它不是某个无礼的值。 (如果它读取旧值,它会在下一个循环中读取新值,对吗?)

【问题讨论】:

这感觉像是 X-Y 问题。你到底想用这两个线程做什么? @AKX 我正在尝试编写一个简单的游戏,主线程将是我的游戏循环,我希望有第二个线程来跟踪 VBlank 何时发生(@987654336 @,它阻塞所以我必须使用另一个线程)所以我可以计算出准确的刷新率。我的显示器说它是 60Hz,但实际上它大约是 59.920Hz,用 UFO TEST 测试过 无争议的lock 花费大约 20 纳秒的 CPU 时间。这对您的心跳线程来说是否有太多延迟? @TheodorZoulias 请阅读我更新的问题,对于我的用例,我可以不使用它吗?使用lock 有什么好处/缺点? (主线程中的循环运行次数与 FPS 一样多,例如,如果游戏以 120FPS 运行,它将每秒循环 120 次,每个循环将读取一次 Interval。) Interlocked Class 【参考方案1】:

即使没有阻塞和非常短的工作,您的计时器线程也会出现滑点,因为它的工作时间不是 0。我认为使用计时器会更好。

请参阅Timer Class 已在内置的多线程设置中运行。

基于服务器的 System.Timers.Timer 类设计用于多线程环境中的工作线程。服务器计时器可以在线程之间移动以处理引发的 Elapsed 事件,从而在按时引发事件方面比 Windows 计时器更准确。

如果 SynchronizingObject 属性为 null,则在 ThreadPool 线程上引发 Elapsed 事件。如果 Elapsed 事件的处理持续时间超过 Interval,则该事件可能会在另一个 ThreadPool 线程上再次引发。在这种情况下,事件处理程序应该是可重入的。

最后:

提示

请注意,.NET 包含四个名为 Timer 的类,每个类都提供不同的功能:

System.Timers.Timer(本主题):定期触发事件。该类旨在用作多线程环境中的基于服务器或服务组件;它没有用户界面,在运行时不可见。 System.Threading.Timer:定期在线程池线程上执行单个回调方法。回调方法是在定时器实例化时定义的,不能更改。与 System.Timers.Timer 类一样,此类旨在用作多线程环境中的基于服务器或服务组件;它没有用户界面,在运行时不可见。 System.Windows.Forms.Timer(仅限 .NET Framework):定期触发事件的 Windows 窗体组件。该组件没有用户界面,设计用于单线程环境。 System.Web.UI.Timer(仅限 .NET Framework):定期执行异步或同步网页回发的 ASP.NET 组件。

【讨论】:

我上面贴的代码是一个非常简化的版本(完整的代码是一团糟...) Interval 已更新。我将编辑问题以添加更多信息。

以上是关于从一个线程读取变量并从另一个线程写入的正确方法?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 PyQt5 中同时读取和写入文件时正确执行多线程?

从另一个线程访问线程本地

c - 一个线程写入一个变量,另一个线程读取它

无法从另一个孩子读取一个孩子写入的共享内存

使用 ZeroMQ Socket 时,我可以从一个线程 send() 并从另一个线程发送 recv() 到同一个套接字吗?

使用 node.js,我如何写入一个 MySQL 数据库并从另一个数据库读取?