14

这只是一个假设的场景来说明我的问题。假设它们之间共享两个线程和一个 TVar。在一个线程中有一个原子块读取 TVar 并需要 10 秒才能完成。在另一个线程中是一个原子块,它每秒修改一次 TVar。第一个原子块会完成吗?当然它会一直回到开头,因为日志永远处于不一致的状态?

4

3 回答 3

13

正如其他人所说:理论上没有进步的保证。在实践中,也不能保证进步:

import Control.Monad -- not needed, but cleans some things up
import Control.Monad.STM
import Control.Concurrent.STM
import Control.Concurrent
import GHC.Conc
import System.IO

main = do
    tv <- newTVarIO 0
    forkIO (f tv)
    g tv

f :: TVar Int -> IO ()
f tv = forever $ do
    atomically $ do
            n <- readTVar tv
            writeTVar tv (n + 1)
            unsafeIOToSTM (threadDelay 100000)
    putStr "."
    hFlush stdout

g :: TVar Int -> IO ()
g tv = forever $ do
    atomically $ do
            n <- readTVar tv
            writeTVar tv (n + 1)
            unsafeIOToSTM (threadDelay 1000000)
    putStrLn "Done with long STM"

在我的测试中,上面从未说过“用长 STM 完成”。

显然,如果您认为计算仍然有效/相关,那么您可能想要

  1. 离开原子块,执行昂贵的计算,进入原子块/确认假设有效/并更新值。潜在危险,但不比大多数锁定策略更危险。
  2. 在原子块中记忆结果,因此仍然有效的结果将只不过是重试后的廉价查找。
于 2010-06-13T21:46:22.597 回答
5

STM 可以防止死锁,但仍然容易受到饥饿的影响。在病态的情况下,1s 原子动作总是获取资源是可能的。

然而,这种情况发生的变化是非常罕见的——我相信我在实践中从未见过。

有关语义,请参阅Composable Memory Transactions,第 6.5 节“进度”。Haskell 中的 STM 仅保证正在运行的事务将成功提交(即没有死锁),但在最坏的情况下,无限事务会阻塞其他事务。

于 2010-06-13T21:17:26.633 回答
2

不,它会工作得很好。两个线程如何交互取决于重试逻辑。

例如,假设您有:

ten tv = do
  n <- readTVar tv
  when (n < 7) retry
  writeTVar tv 0
  -- do something that takes about 10 seconds

one tv = do
  modifyTVar tv (+1)
  -- do something that takes about 1 second

因此“ ten”线程将处于重试状态,直到 TVar 达到值 7,然后它将继续。

请注意,您无法直接控制这些计算在 STM monad 中需要多长时间。那将是一个副作用,并且在 STM 计算中不允许有副作用。与外部世界通信的唯一方式是通过事务内存传递的值。

这意味着,如果通过事务内存的“接力棒”逻辑是正确的,那么程序将独立于其任何部分所花费的确切时间量而正常工作。这是STM保证的一部分。

于 2010-06-13T11:09:51.367 回答