您的代码在编译之前就存在许多问题:
-
damageCalculation 尝试将 life 用作 MVar Integer(即包含整数的存储位置的“令牌”)和 Integer 值本身
- 您只向
swapMVar 提供了一个参数,而它需要两个参数
- 你混合了
Int 和 Integer 类型
- 在
main 中,您将 damageCalculation 视为纯函数而不是 I/O 操作
修复所有这些错误将允许程序进行类型检查(参见下面的清单 #1)。
但是,仍然存在一些错误。 首先,代码:
a <- readMVar life
let newLife = a - randDano
a <- swapMVar life newLife
如果多个线程尝试更新损坏,则受竞争条件的影响。第一行读取 MVar 的值,但不执行任何类型的锁定或同步,然后第三行无条件地写入新生命(同时获取旧值的副本)。
例如,如果两个线程尝试以以下交错方式分别扣除 10 点伤害,您就会遇到问题:
Thread 1 Thread 2
-------- --------
a <- readMVar life -- fetch 100 life
a <- readMVar life -- fetch 100 life again
let newLife = ... -- deduct 10 to get 90
a <- swapMVar life newLife -- save 90 in the MVar
let newLife = ... -- deduct 10 to get 90
a <- swapMVar life newLife -- save 90 in the MVar
相反,您想使用 takeMVar 和 putMVar 这对旨在提供自动同步的函数:
Thread 1 Thread 2
-------- --------
a <- takeMVar life -- fetch 100 life
a <- takeMVar life -- nothing there, so block...
let newLife = ... -- BLOCKING -- deduct 10 to get 90
a <- putMVar life newLife -- BLOCKING -- save 90 in the MVar
-- WAKE UP -- fetch 90 from MVar
let newLife = ... -- deduct 10 to get 80
a <- putMVar life newLife -- save 80 in the MVar
在my answer to your other question 中,我曾推荐使用readMVar 和swapMVar,但是如果您查看该代码,您会发现情况非常不同——一个线程只需要读取当前值MVar(例如,读取wHeld 以查看是否按下了“W”),并且另一个 线程需要无条件写入一个新值(例如,写入wHeld 以更新当前状态“W”键)。不需要锁定,因为您只有两个线程,一个始终在写入,一个始终读取最近写入的内容。
damageCalculation 的 第二个 问题是我认为您想返回最终生命值,但您返回的是 swapMVar 调用的结果,这将是 扣除伤害前的旧寿命值。换句话说,您可能希望 return newLife 作为您的 do-block 的最后一行。
第三个问题是您使用StdGen 的方式。当你写:
let (randDano, newGen) = randomR (15,30) gen :: (Int, StdGen)
gen 的值用于创建随机的randDano 值,然后返回生成器newGen 的更新值。如果您将这个newGen 扔掉并尝试再次重用gen,您将始终生成相同的randDano 值。您可以将newGen 作为damageCalculation 的返回值的一部分并将其用于下一次调用,或者您可以将生成器本身设置为由damageCalculation 函数更新的MVar。由于您想练习使用 MVar,因此后者似乎是可行的方法。因此,您的 damageCalculation 函数应如下所示:
damageCalculation :: MVar StdGen -> MVar Int -> IO Int
damageCalculation v_gen v_life = do
gen <- takeMVar v_gen
let (randDano, gen') = randomR (15,30) gen
putMVar v_gen gen'
life <- takeMVar v_life
let life' = life - randDano
putMVar v_life life'
return life'
注意takeMVar/修改值/putMVar模式的使用。
在main 函数中,您可能希望在分叉之前预先创建所有相关的 MVar,例如:
main :: IO ()
main = do
putStrLn "Welcome to Hunter of Monsters"
putStrLn "Whats your name?"
l <- getLine
putStrLn "Character"
putStrLn ("Name: " ++ l)
gen <- getStdGen
v_gen <- newMVar gen
putStrLn $ "Life Points: " ++ show lifePoints
v_life <- newMVar lifePoints
done <- newEmptyMVar
putStrLn "The hunt begins"
-- damage stuff here --
takeMVar done
putStrLn "Game over!"
对于伤害计算,如果你想继续伤害直到生命值用完,定义一个这样的循环函数然后forkIO它:
let doDamage = do
putStrLn "Damage calculation"
rest <- damageCalculation v_gen v_life
putStrLn $ " " ++ show rest
if rest > 0
then doDamage
else putMVar done ()
forkIO doDamage
完整的程序在下面的清单 #2 中。
清单 #1:让程序进行类型检查
import Control.Concurrent
import System.Random
lifePoints :: Int -- use Int throughout
lifePoints = 100
damageCalculation :: StdGen -> MVar Int -> IO Int -- use Int throughout
damageCalculation gen life = do
let (randDano, newGen) = randomR (15,30) gen :: (Int, StdGen)
a <- readMVar life
let newLife = a - randDano -- Use "a" (the value), not "life" (the MVar)
a <- swapMVar life newLife -- "swapMVar" needs an MVar ("life"), not just a value
return a
main :: IO ()
main = do
putStr "Welcome to Hunter of Monsters\n"
putStr "Whats your name? \n"
l <- getLine
putStr "Character\n"
putStrLn ("Name: " ++ l)
let life = lifePoints
putStrLn $ "Life Points: " ++ show life
putStr "The hunt begins\n"
a <- newEmptyMVar
forkIO $ do
putStr "Damage calculation\n"
lifeMVar <- newMVar life
gen <- getStdGen
rest <- damageCalculation gen lifeMVar -- IO action, so use '<-' not 'let'
putStrLn $ " " ++ show rest
putMVar a ()
takeMVar a
putStrLn "Game over!"
清单 #2:带有损坏循环的最终程序
import Control.Concurrent
import System.Random
lifePoints :: Int
lifePoints = 100
damageCalculation :: MVar StdGen -> MVar Int -> IO Int
damageCalculation v_gen v_life = do
gen <- takeMVar v_gen
let (randDano, gen') = randomR (15,30) gen
putMVar v_gen gen'
life <- takeMVar v_life
let life' = life - randDano
putMVar v_life life'
return life'
main :: IO ()
main = do
putStrLn "Welcome to Hunter of Monsters"
putStrLn "Whats your name?"
l <- getLine
putStrLn "Character"
putStrLn ("Name: " ++ l)
gen <- getStdGen
v_gen <- newMVar gen
putStrLn $ "Life Points: " ++ show lifePoints
v_life <- newMVar lifePoints
done <- newEmptyMVar
putStrLn "The hunt begins"
let doDamage = do
putStrLn "Damage calculation"
rest <- damageCalculation v_gen v_life
putStrLn $ " " ++ show rest
if rest > 0
then doDamage
else putMVar done ()
forkIO doDamage
takeMVar done
putStrLn "Game over!"