2010-06-13 22 views
13

Đây chỉ là một kịch bản giả định để minh họa cho câu hỏi của tôi. Giả sử rằng có hai luồng và một TVar được chia sẻ giữa chúng. Trong một chủ đề có một khối nguyên tử đọc TVar và mất 10 giây để hoàn thành. Trong một chủ đề khác là một khối nguyên tử mà sửa đổi TVar mỗi giây. Khối nguyên tử đầu tiên có hoàn thành không? Chắc chắn nó sẽ chỉ tiếp tục quay trở lại ban đầu, bởi vì nhật ký là vĩnh viễn trong một trạng thái không nhất quán?STM monad problem

Trả lời

12

Như những người khác đã nói: về lý thuyết không có đảm bảo về tiến độ. Trong thực tế, cũng không đảm bảo tiến độ:

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" 

Ở trên không bao giờ nói "Đã xong với STM dài" trong thử nghiệm của tôi.

Rõ ràng nếu bạn nghĩ việc tính toán vẫn sẽ có giá trị/thích hợp sau đó bạn sẽ muốn một trong hai

  1. Rời khỏi khối nguyên tử, thực hiện tính toán đắt tiền, nhập khối nguyên tử/xác nhận giả định là hợp lệ/và cập nhật giá trị. Có tiềm năng nguy hiểm, nhưng không nhiều hơn so với hầu hết các chiến lược khóa.
  2. Ghi nhớ kết quả trong khối nguyên tử để kết quả vẫn hợp lệ sẽ không quá một tra cứu giá rẻ sau khi thử lại.
+2

Ví dụ tuyệt vời. Tôi muốn thử nghiệm với một cái gì đó như thế này, nhưng tôi đã không nhận thức được chức năng 'unsafeIOToSTM'! – Alex

2

Không, nó sẽ hoạt động tốt. Chính xác cách hai luồng sẽ tương tác phụ thuộc vào logic thử lại.

Ví dụ, giả sử bạn có:

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 

Vì vậy, các "ten" chủ đề sẽ được ở trạng thái thử lại cho đến khi đạt TVar giá trị 7, sau đó nó sẽ được tiến hành.

Lưu ý rằng bạn không thể kiểm soát trực tiếp thời gian các tính toán này sẽ mất bên trong đơn vị STM. Đó sẽ là một tác dụng phụ, và tác dụng phụ không phải là được phép trong các phép tính STM. Cách duy nhất để giao tiếp với thế giới bên ngoài là thông qua các giá trị được truyền qua bộ nhớ giao dịch. Và điều đó có nghĩa là nếu logic "đi qua baton" qua bộ nhớ giao dịch là đúng, chương trình sẽ hoạt động chính xác độc lập với số tiền chính xác của mọi thời gian cần thiết. Đó là một phần của sự bảo đảm của STM.

+0

Tôi thực sự đang nghĩ đến trường hợp xấu nhất. Hãy quên retrys một lúc và chỉ nghĩ về hai luồng, và xem xét việc thực thi STM 'mười'. Nó đọc TVar và cam kết giá trị đó cho nhật ký. Trong khi đó, các chủ đề khác thay đổi rằng TVar luôn trong quá trình thực hiện 'mười'.Vì vậy, khi kết thúc việc thực hiện 'mười', giá trị trong TV thực không giống như giá trị ban đầu được đọc trong 'mười', buộc 'mười' để khởi tạo lại nhật ký và thực thi lại mọi lúc. – Alex

+1

Như Yitz đã nói, nó phụ thuộc. Có, nó có thể xây dựng một tình huống mà một giao dịch không bao giờ có thể hoàn thành. Hoặc chính thức hơn, STM không đảm bảo tiến độ. –

5

STM ngăn chặn bế tắc, nhưng vẫn dễ bị đói. Có thể trong trường hợp bệnh lý cho hành động nguyên tử 1s để luôn thu hút tài nguyên.

Tuy nhiên, những thay đổi của sự cố này rất hiếm - tôi không tin rằng mình từng thấy nó trong thực tế.

Để biết ngữ nghĩa, hãy xem Composable Memory Transactions, phần 6.5 "Tiến trình". STM trong Haskell chỉ đảm bảo rằng một giao dịch đang chạy sẽ thực hiện thành công (nghĩa là không có bế tắc), nhưng trong trường hợp xấu nhất, một giao dịch vô hạn sẽ chặn các giao dịch khác.

+0

Cảm ơn bạn đã tham khảo. – Alex

+0

STM không nhất thiết không bị chặn. –