使用线程时如何减少时间偏差?

Posted

技术标签:

【中文标题】使用线程时如何减少时间偏差?【英文标题】:How to reduce the time deviation when using threads? 【发布时间】:2022-01-16 20:43:34 【问题描述】:

这是尝试编写一段简单的代码,它将获取当前时间并假设在适当的时候触发一个函数。

-# LANGUAGE BlockArguments, NumericUnderscores #-

module Main where

import Control.Concurrent
import Control.Monad (forever, forM, void)
import Data.Time.Clock

main :: IO ()
main = forever do
    forkIO writer
    threadDelay 1_000_000

writer :: IO ()
writer = print =<< getCurrentTime

得到这个:

2021-12-13 09:22:08.7632491 UTC
2021-12-13 09:22:09.7687358 UTC
2021-12-13 09:22:10.7756821 UTC
2021-12-13 09:22:11.7772306 UTC
2021-12-13 09:22:12.7954329 UTC
2021-12-13 09:22:13.8096189 UTC
2021-12-13 09:22:14.8218579 UTC
2021-12-13 09:22:15.826626 UTC
2021-12-13 09:22:16.8291541 UTC
2021-12-13 09:22:17.8358406 UTC
2021-12-13 09:22:18.8468617 UTC
2021-12-13 09:22:19.8490381 UTC
2021-12-13 09:22:20.859682 UTC
2021-12-13 09:22:21.868705 UTC
2021-12-13 09:22:22.88392 UTC
2021-12-13 09:22:23.8893969 UTC
2021-12-13 09:22:24.8940725 UTC
2021-12-13 09:22:25.9026013 UTC
2021-12-13 09:22:26.9181843 UTC
2021-12-13 09:22:27.920115 UTC
2021-12-13 09:22:28.9214061 UTC
2021-12-13 09:22:29.9236218 UTC
2021-12-13 09:22:30.9320501 UTC
2021-12-13 09:22:31.9359116 UTC
2021-12-13 09:22:32.9381218 UTC
2021-12-13 09:22:33.9541171 UTC
2021-12-13 09:22:34.9639691 UTC
2021-12-13 09:22:35.9767943 UTC
2021-12-13 09:22:36.9909998 UTC
2021-12-13 09:22:38.0016628 UTC
2021-12-13 09:22:39.0029746 UTC
2021-12-13 09:22:40.01921 UTC
2021-12-13 09:22:41.0337936 UTC
2021-12-13 09:22:42.0369494 UTC
2021-12-13 09:22:43.0403321 UTC
2021-12-13 09:22:44.0426835 UTC
2021-12-13 09:22:45.0468416 UTC
2021-12-13 09:22:46.0503551 UTC
2021-12-13 09:22:47.0557148 UTC
2021-12-13 09:22:48.066979 UTC
2021-12-13 09:22:49.0723431 UTC

您可能已经注意到,这些差异并不准确,timedif 中的错误对我来说可能至关重要。有什么办法可以改善吗?

当不同的线程使用打印功能时尝试了该选项,但从长远来看几乎没有什么区别。

谢谢!

【问题讨论】:

自己修复漂移。 ***.com/questions/7435771/…***.com/questions/33611149/…***.com/questions/56190913/… 【参考方案1】:

现在,这是您最初问题的答案。秘诀在于,不要总是在事件之间等待一秒钟,而应该跟踪触发时间,始终将其递增一秒,并等待到达下一个触发时间所需的任何时间。它实际上在许多方面与我的其他答案相似:

-# LANGUAGE NumericUnderscores #-

module Main where

import Control.Concurrent
import Control.Monad
import Data.Time

main :: IO ()
main = everySecond =<< getCurrentTime

everySecond :: UTCTime -> IO ()
everySecond tick = do
  forkIO writer
  -- next tick in one second
  let nexttick = addUTCTime (secondsToNominalDiffTime 1) tick
  now <- getCurrentTime
  let wait = nominalDiffTimeToSeconds (diffUTCTime nexttick now)
  threadDelay $ ceiling (wait * 1_000_000)
  everySecond nexttick

writer :: IO ()
writer = print =<< getCurrentTime

样本输出:

2021-12-13 21:16:53.316687476 UTC
2021-12-13 21:16:54.318070692 UTC
2021-12-13 21:16:55.31821399 UTC
2021-12-13 21:16:56.318432887 UTC
2021-12-13 21:16:57.318432582 UTC
2021-12-13 21:16:58.318648861 UTC
2021-12-13 21:16:59.317988137 UTC
2021-12-13 21:17:00.318367675 UTC
2021-12-13 21:17:01.318565036 UTC
2021-12-13 21:17:02.317856019 UTC
2021-12-13 21:17:03.318285608 UTC
2021-12-13 21:17:04.318508451 UTC
2021-12-13 21:17:05.318487069 UTC
2021-12-13 21:17:06.318435325 UTC
2021-12-13 21:17:07.318504691 UTC
2021-12-13 21:17:08.318591666 UTC
2021-12-13 21:17:09.317797443 UTC
2021-12-13 21:17:10.317732578 UTC
2021-12-13 21:17:11.318100396 UTC
2021-12-13 21:17:12.318535002 UTC
2021-12-13 21:17:13.318008916 UTC
2021-12-13 21:17:14.317803441 UTC
2021-12-13 21:17:15.318220664 UTC
2021-12-13 21:17:16.318558786 UTC
2021-12-13 21:17:17.31793816 UTC
2021-12-13 21:17:18.322564881 UTC
2021-12-13 21:17:19.318923334 UTC
2021-12-13 21:17:20.318293808 UTC

【讨论】:

【参考方案2】:

不完全回答您的问题,但如果您想编写一个程序以在特定时间触发事件,更健壮的设计是:

    按时间对(时间、事件)对列表进行排序 休眠列表中的第一个事件时间和当前时间之间的差异 当您醒来时,获取/更新当前时间,然后执行并从列表的前面删除所有时间已“过期”的事件(即当前时间或之前的事件时间)时间)。 如果列表仍为非空,请跳至第 2 步。

这避免了每秒轮询的需要(这可能不是什么大不了的事,但仍然......)并避免了由于您比预期更晚醒来而错过事件的可能性。

下面是一个示例程序。 (此程序依赖threadDelay 将负数视为零,以防事件需要很长时间才能运行,而实际时间超过了第一个未过期的事件。)

-# LANGUAGE NumericUnderscores #-

import Data.List
import Data.Time
import Control.Concurrent

data Event = Event
   eventTime :: UTCTime
  , eventAction :: IO ()
  

runEvents :: [Event] -> IO ()
runEvents = go . sortOn eventTime
  where go [] = return ()  -- no more events
        go events@(Event firstTime _ : _) = do
          now <- getCurrentTime
          let wait = nominalDiffTimeToSeconds (diffUTCTime firstTime now)
          threadDelay $ ceiling (wait * 1_000_000)
          now' <- getCurrentTime
          let (a, b) = span (expiredAsOf now') events
          mapM eventAction a  -- run the expired events
          go b  -- wait for the rest

        expiredAsOf t e = eventTime e <= t

main = do
  -- some example events
  now <- getCurrentTime
  let afterSeconds = flip addUTCTime now . secondsToNominalDiffTime
      evts = [ Event (afterSeconds 3) (putStrLn "3 seconds")
             , Event (afterSeconds 6) (putStrLn "6 seconds action # 1")
             , Event (afterSeconds 6) (putStrLn "6 seconds action # 2")
             , Event (afterSeconds 7) (putStrLn "Done after 7 seconds")
             ]
  runEvents evts

【讨论】:

以上是关于使用线程时如何减少时间偏差?的主要内容,如果未能解决你的问题,请参考以下文章

机器学习--偏差和方差

使用 k-means 聚类时如何设置 spark 配置以减少洗牌?

如何使用 fetch_sub 和 atomic_thread_fence 减少多线程?

在 Java 中完成任务时线程在增加但不减少

如何在不使用 numpy 的情况下计算 python 中的标准偏差?

15. 偏差-方差权衡(续)