我对par的理解是,它将在另一个执行核心中创建一个线程。

但是我无法通过以下测试代码来证明这种理解,因为结果显示似乎只有一个线程正在运行。

您能帮我弄清楚这里出什么问题了吗?

import Control.Monad
import Control.Parallel
import Control.Concurrent
import System.IO.Unsafe

fib :: Int -> Int
fib 0 = 1
fib 1 = 1
fib n = (fib (n-1)) + (fib (n - 2))

test :: String -> [Int] -> IO ()
test _ [] = return ()
test name (a:xs) = do
    tid <- myThreadId
    print $ (show tid) ++ "==>" ++ (show a) ++ "==>" ++ (show $ fib a) ++ "==>" ++ name
    x `par` y
  where x = test "2" xs
        y = test "3" (tail xs)

main = test "1" [10..35]

编译:
ghc --make -threaded -rtsopts test-par.hs
time ./test-par +RTS -N2

结果
"ThreadId 3==>10==>89==>1"
"ThreadId 3==>12==>233==>3"
"ThreadId 3==>14==>610==>3"
"ThreadId 3==>16==>1597==>3"
"ThreadId 3==>18==>4181==>3"
"ThreadId 3==>20==>10946==>3"
"ThreadId 3==>22==>28657==>3"
"ThreadId 3==>24==>75025==>3"
"ThreadId 3==>26==>196418==>3"
"ThreadId 3==>28==>514229==>3"
"ThreadId 3==>30==>1346269==>3"
"ThreadId 3==>32==>3524578==>3"
"ThreadId 3==>34==>9227465==>3"

real    0m1.131s
user    0m0.668s
sys 0m0.492s

我有几个核心?
cat /proc/cpuinfo | grep processor | wc -l
2

- - - - - - - - - - - - - - - - 更新

我认为this paper by Simon Marlow是此类新手问题的很好引用。

最佳答案

不,par不保证创建另一个线程。

它会在运行时记录 Spark ,这可能导致计算在不同的线程中执行,具体取决于计算机的动态工作负载。

创建 Spark 非常便宜,因此您可以创建的线程数多于线程数(1000x),运行时将尝试使所有内核保持忙碌状态。

在您的情况下,您的x计算被注册为 Spark ,然后立即被丢弃(您再也不会引用它了)。因此垃圾收集器可以将其删除。

要并行化递归函数,通常需要使用par直到一定深度。

一个示例-具有截止深度的递归函数:

import Control.Parallel
import Control.Monad
import Text.Printf

cutoff = 35

fib' :: Int -> Integer
fib' 0 = 0
fib' 1 = 1
fib' n = fib' (n-1) + fib' (n-2)

fib :: Int -> Integer
fib n | n < cutoff = fib' n
      | otherwise  = r `par` (l `pseq` l + r)
 where
    l = fib (n-1)
    r = fib (n-2)

main = forM_ [0..45] $ \i ->
            printf "n=%d => %d\n" i (fib i)

运行方式:
$ ghc -O2 -threaded --make A.hs -rtsopts -fforce-recomp
$ ./A +RTS -N16 -s

连续产生的并行工作负载为1248.9%(即12.48x):
                                   Tot time (elapsed)  Avg pause  Max pause
  Gen  0      6264 colls,  6263 par    5.23s    0.41s     0.0001s    0.0109s
  Gen  1         1 colls,     1 par    0.00s    0.00s     0.0002s    0.0002s

  Parallel GC work balance: 7.09 (1797194 / 253525, ideal 16)

                        MUT time (elapsed)       GC time  (elapsed)
  Task  0 (worker) :    7.56s    (  9.36s)       0.92s    (  0.89s)
  Task  1 (worker) :    0.12s    ( 10.21s)       0.02s    (  0.05s)
  Task  2 (bound)  :    8.39s    (  9.51s)       0.70s    (  0.74s)
  Task  3 (worker) :    0.00s    (  0.00s)       0.00s    (  0.00s)
  Task  4 (worker) :    7.17s    (  9.60s)       0.53s    (  0.66s)
  Task  5 (worker) :    7.28s    (  9.55s)       0.56s    (  0.71s)
  Task  6 (worker) :    7.48s    (  9.52s)       0.56s    (  0.74s)
  Task  7 (worker) :    7.11s    (  9.54s)       0.66s    (  0.72s)
  Task  8 (worker) :    7.41s    (  9.62s)       0.70s    (  0.64s)
  Task  9 (worker) :    7.69s    (  9.48s)       0.66s    (  0.78s)
  Task 10 (worker) :    7.56s    (  9.51s)       0.56s    (  0.75s)
  Task 11 (worker) :    7.69s    (  9.42s)       0.86s    (  0.84s)
  Task 12 (worker) :    7.42s    (  9.40s)       0.92s    (  0.86s)
  Task 13 (worker) :    7.28s    (  9.39s)       0.91s    (  0.86s)
  Task 14 (worker) :    7.44s    (  9.38s)       0.91s    (  0.87s)
  Task 15 (worker) :    7.25s    (  9.33s)       1.11s    (  0.93s)
  Task 16 (worker) :    7.94s    (  9.33s)       0.97s    (  0.93s)
  Task 17 (worker) :    7.59s    (  9.37s)       1.06s    (  0.88s)

  SPARKS: 597 (446 converted, 0 dud, 1 GC'd, 150 fizzled)

  Productivity  96.1% of total user, 1245.3% of total elapsed

我们创建了597个 Spark ,其中446个已转换为线程。

如果您明确希望进行手动线程创建和通信,则可以通过forkIOMVars完成。

关于multithreading - `par`是否创建另一个线程?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/10724946/

10-11 23:15
查看更多