2013-06-13 45 views
7

Tôi đang làm việc với STM và có một số thứ khác sử dụng cấu trúc dữ liệu TBQueue với thành công lớn. Một tính năng hữu ích tôi đã sử dụng nó cho liên quan đến việc đọc từ nó dựa trên một điều kiện tiên quyết trong một TVar, về cơ bản giống như vậy:STM với một phần nguyên tử đối với một số TV nhất định

shouldRead <- readTVar shouldReadVar 
if shouldRead 
    then do 
    a <- readTBQueue queue 
    doSomethingWith a 
    else doSomethingElse 

Nếu chúng ta giả định rằng queue trống và shouldReadVar chứa True trước khi thực hiện khối này, nó sẽ dẫn đến readTBQueue gọi retry và khối sẽ được thực thi lại khi shouldReadVar chứa False hoặc queue chứa phần tử, bất kỳ điều gì xảy ra trước.


Tôi bây giờ đang cần một cấu trúc dữ liệu kênh đồng bộ, tương tự như cấu trúc được mô tả trong this article (Xin đọc nó nếu bạn muốn hiểu câu hỏi này), ngoại trừ nó cần phải được đọc với một điều kiện tiên quyết như trong ví dụ trước, và có thể sáng tác với các công cụ khác.

Hãy gọi cấu trúc dữ liệu này SyncChan với các hoạt động writeSyncChanreadSyncChan được xác định trên đó.

Và đây là một trường hợp sử dụng càng tốt: Đây (giả) mã (mà sẽ không làm việc vì tôi trộn khái niệm STM/IO):

shouldRead <- readTVar shouldReadVar 
if shouldRead 
    then do 
    a <- readSyncChan syncChan 
    doSomethingWith a 
    else doSomethingElse 

Giả sử rằng không có thread khác hiện đang chặn trên một cuộc gọi writeSyncChan, và shouldReadChan chứa True, tôi muốn chặn thành "retry" cho đến khi shouldReadChan chứa False, hoặc một khối chuỗi khác trên writeSyncChan. Nói cách khác: khi một sợi retry s trên writeSyncChan và một khối chuỗi khác đạt đến readSyncChan hoặc ngược lại, tôi muốn giá trị được chuyển dọc theo kênh. Trong tất cả các trường hợp khác, cả hai bên phải ở trạng thái retry và do đó phản ứng với thay đổi trong shouldReadVar, để đọc hoặc ghi có thể bị hủy.

Cách tiếp cận ngây thơ được mô tả trong bài viết được liên kết ở trên sử dụng hai (T) Tất nhiên là không thể thực hiện được MVar s. Vì cấu trúc dữ liệu là đồng bộ, nên không thể sử dụng nó trong hai khối atomically, bởi vì bạn không thể thay đổi một số TMVar và đợi TMVar khác thay đổi trong ngữ cảnh nguyên tử. Thay vào đó, tôi đang tìm kiếm một loại nguyên tử một phần, nơi tôi có thể "cam kết" một phần nhất định của một giao dịch và chỉ cuộn lại khi một số biến nhất định thay đổi, nhưng không phải là biến số khác. Nếu tôi có các biến "msg" và "ack" giống như ví dụ đầu tiên trong bài viết ở trên, tôi muốn có thể ghi vào biến "msg", sau đó chờ giá trị đến "ack" hoặc cho các biến giao dịch khác để thay đổi. Nếu các biến giao dịch khác thay đổi, toàn bộ khối nguyên tử sẽ được thử lại và nếu một giá trị "ack" đến, giao dịch sẽ tiếp tục như ở trạng thái trước đó. Đối với phía đọc, một cái gì đó tương tự sẽ xảy ra, ngoại trừ tôi tất nhiên sẽ được đọc từ "msg" và viết thư cho "ack".

Điều này có thể thực hiện bằng cách sử dụng GHC STM hay tôi có cần thực hiện xử lý MVar/rollback thủ công không?

Trả lời

3

Đây là những gì bạn muốn:

import Control.Concurrent 
import Control.Concurrent.STM 
import Control.Monad 

data SyncChan a = SyncChan (TMVar a) (TMVar()) 

newSyncChan :: IO (SyncChan a) 
newSyncChan = do 
    msg <- newEmptyTMVarIO 
    ack <- newEmptyTMVarIO 
    return (SyncChan msg ack) 

readIf :: SyncChan a -> TVar Bool -> STM (Maybe a) 
readIf (SyncChan msg ack) shouldReadVar = do 
    b <- readTVar shouldReadVar 
    if b 
     then do 
      a <- takeTMVar msg 
      putTMVar ack() 
      return (Just a) 
     else return Nothing 

write :: SyncChan a -> a -> IO() 
write (SyncChan msg ack) a = do 
    atomically $ putTMVar msg a 
    atomically $ takeTMVar ack 

main = do 
    sc <- newSyncChan 
    tv <- newTVarIO True 
    forkIO $ forever $ forM_ [False, True] $ \b -> do 
     threadDelay 2000000 
     atomically $ writeTVar tv b 
    forkIO $ forM_ [0..] $ \i -> do 
     putStrLn "Writing..." 
     write sc i 
     putStrLn "Write Complete" 
     threadDelay 300000 
    forever $ do 
     putStrLn "Reading..." 
     a <- atomically $ readIf sc tv 
     print a 
     putStrLn "Read Complete" 

này mang lại cho hành vi mà bạn có trong tâm trí. Trong khi TVarTrue đầu vào và đầu ra sẽ được đồng bộ hóa với nhau. Khi TVar chuyển sang False thì đầu đọc sẽ tự do hủy và trả lại Nothing.

+0

Bạn cho rằng có cấu trúc dữ liệu được gọi là 'SyncChan' với các ngữ nghĩa nhất định. Tuy nhiên, không có cấu trúc dữ liệu như vậy; vấn đề xuất hiện khi cố gắng thực hiện nó. Về cơ bản, bạn đã lấy mã từ khối mã thứ hai của tôi trong câu hỏi và trích nhánh vào một giá trị 'Có thể'. Vấn đề thực tế nằm ở việc thực hiện 'readSyncChan' và' writeSyncChan'! – dflemstr

+0

@dflemstr Tôi đã sửa nó và viết lên toàn bộ việc triển khai, bao gồm cả mã sử dụng ví dụ. –

+0

cảm ơn bạn đã dành thời gian viết tất cả mã này. Tuy nhiên, ở đây nó không thể làm điều kiện viết (với một 'shouldWriteVar', để nói). Nó không hoạt động bằng cách chỉ cần thêm vào khối 'nguyên tử' đầu tiên trong hàm' write', bởi vì nếu một giá trị đã được viết ahd, luồng đang chờ 'ack', không có cách nào để trả lời thay đổi trong 'shouldWriteVar'! Là cách sạch nhất để kiểm tra 'shouldWriteVar' một lần nữa ở đây, hoặc là có một số tùy chọn khác mà tránh một số tình huống bế tắc lạ tôi đã không xem xét? – dflemstr