1
1
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
2
2
{-# LANGUAGE CPP, DeriveDataTypeable #-}
3
+ {-# LANGUAGE BangPatterns #-}
3
4
4
5
#if __GLASGOW_HASKELL__ >= 701
5
6
{-# LANGUAGE Trustworthy #-}
17
18
--
18
19
-- A 'TQueue' is like a 'TChan', with two important differences:
19
20
--
20
- -- * it has faster throughput than both 'TChan' and 'Chan' (although
21
- -- the costs are amortised, so the cost of individual operations
22
- -- can vary a lot).
21
+ -- * it has faster throughput than both 'TChan' and 'Chan'
23
22
--
24
23
-- * it does /not/ provide equivalents of the 'dupTChan' and
25
24
-- 'cloneTChan' operations.
26
25
--
27
- -- The implementation is based on the traditional purely-functional
28
- -- queue representation that uses two lists to obtain amortised /O(1)/
29
- -- enqueue and dequeue operations .
26
+ -- The implementation is based on Okasaki's scheduled banker's queues,
27
+ -- but it uses * two* schedules so there's only contention between the
28
+ -- reader and writer when the queue needs to be rotated .
30
29
--
31
30
-- @since 2.4
32
31
-----------------------------------------------------------------------------
@@ -44,63 +43,109 @@ module Control.Concurrent.STM.TQueue (
44
43
writeTQueue ,
45
44
unGetTQueue ,
46
45
isEmptyTQueue ,
47
- ) where
46
+ ) where
48
47
49
48
import GHC.Conc
50
49
import Control.Monad (unless )
51
50
import Data.Typeable (Typeable )
52
51
52
+ data End a =
53
+ End [a ] -- list
54
+ [a ] -- schedule
55
+
53
56
-- | 'TQueue' is an abstract type representing an unbounded FIFO channel.
54
57
--
55
58
-- @since 2.4
56
- data TQueue a = TQueue {- # UNPACK #-} !(TVar [ a ] )
57
- {- # UNPACK #-} !(TVar [ a ] )
59
+ data TQueue a = TQueue {- # UNPACK #-} !(TVar ( End a ) )
60
+ {- # UNPACK #-} !(TVar ( End a ) )
58
61
deriving Typeable
62
+ {-
63
+ Invariant:
64
+
65
+ Given front list, rear list, front schedule, and rear schedule called
66
+ front, rear, fsched, and rsched, respectively,
67
+
68
+ 2 * (|front| - |rear|) = |fsched| + |rsched|
69
+
70
+ Note that because lengths cannot be negative, this implies that
71
+
72
+ |front| >= |rear|
73
+
74
+ We rotate the queue when either schedule is empty. This preserves
75
+ the invariant and ensures that the spine of the front list is
76
+ fully realized when a rotation occurs. The spine of the rear list
77
+ is *always* fully realized. We could use a strict-spined list for
78
+ the rear, but it doesn't really seem to be worth the trouble.
79
+ -}
59
80
60
81
instance Eq (TQueue a ) where
61
82
TQueue a _ == TQueue b _ = a == b
62
83
63
- -- | Build and returns a new instance of 'TQueue'
84
+ -- | Build and returns a new instance of 'TQueue'
64
85
newTQueue :: STM (TQueue a )
65
86
newTQueue = do
66
- read <- newTVar []
67
- write <- newTVar []
87
+ read <- newTVar ( End [] [] )
88
+ write <- newTVar ( End [] [] )
68
89
return (TQueue read write)
69
90
70
- -- | @IO@ version of 'newTQueue'. This is useful for creating top-level
91
+ -- | @IO@ version of 'newTQueue'. This is useful for creating top-level
71
92
-- 'TQueue's using 'System.IO.Unsafe.unsafePerformIO', because using
72
93
-- 'atomically' inside 'System.IO.Unsafe.unsafePerformIO' isn't
73
94
-- possible.
74
95
newTQueueIO :: IO (TQueue a )
75
96
newTQueueIO = do
76
- read <- newTVarIO []
77
- write <- newTVarIO []
97
+ read <- newTVarIO ( End [] [] )
98
+ write <- newTVarIO ( End [] [] )
78
99
return (TQueue read write)
79
100
80
- -- | Write a value to a 'TQueue'.
101
+ -- rotate front end = front ++ reverse rear, but the reverse is performed
102
+ -- incrementally as the append proceeds.
103
+ --
104
+ -- Precondition: |front| + 1 >= |rear|. This ensures that when the front
105
+ -- list is empty, the rear list has at most one element, so we don't need
106
+ -- to reverse it.
107
+ rotate :: [a ] -> [a ] -> [a ]
108
+ rotate = go []
109
+ where
110
+ go acc [] rear = rear ++ acc
111
+ go acc (x: xs) (r: rs)
112
+ = x : go (r: acc) xs rs
113
+ go acc xs [] = xs ++ acc
114
+
115
+ -- | Write a value to a 'TQueue'.
81
116
writeTQueue :: TQueue a -> a -> STM ()
82
- writeTQueue (TQueue _read write) a = do
83
- listend <- readTVar write
84
- writeTVar write (a: listend)
85
-
86
- -- | Read the next value from the 'TQueue'.
117
+ writeTQueue (TQueue read write) a = do
118
+ End listend rsched <- readTVar write
119
+ let listend' = a : listend
120
+ case rsched of
121
+ -- Reduce |front|-|rear| by 1; reduce |fsched|+|rsched| by 2
122
+ _: _: rsched' -> writeTVar write (End listend' rsched')
123
+
124
+ -- Rotate the queue; the invariant holds trivially.
125
+ _ -> do
126
+ End listfront _fsched <- readTVar read
127
+ let ! front' = rotate listfront listend'
128
+ writeTVar read (End front' front')
129
+ writeTVar write (End [] front')
130
+
131
+ -- | Read the next value from the 'TQueue'.
87
132
readTQueue :: TQueue a -> STM a
88
133
readTQueue (TQueue read write) = do
89
- xs <- readTVar read
90
- case xs of
91
- (x : xs') -> do
92
- writeTVar read xs'
93
- return x
94
- [] -> do
95
- ys <- readTVar write
96
- case ys of
97
- [] -> retry
98
- _ -> do
99
- let (z : zs) = reverse ys -- NB. lazy: we want the transaction to be
100
- -- short, otherwise it will conflict
101
- writeTVar write []
102
- writeTVar read zs
103
- return z
134
+ End listfront fsched <- readTVar read
135
+ case listfront of
136
+ [] -> retry
137
+ x : front' ->
138
+ case fsched of
139
+ -- Reduce |front|-|rear| by 1; reduce |fsched|+|rsched| by 2
140
+ _ : _ : fsched' -> writeTVar read ( End front' fsched') >> return x
141
+
142
+ -- Rotate the queue; the invariant holds trivially.
143
+ _ -> do
144
+ End listend _rsched <- readTVar write
145
+ let ! front'' = rotate front' listend
146
+ writeTVar read ( End front'' front'')
147
+ writeTVar write ( End [] front'')
148
+ return x
104
149
105
150
-- | A version of 'readTQueue' which does not retry. Instead it
106
151
-- returns @Nothing@ if no value is available.
@@ -113,44 +158,38 @@ tryReadTQueue c = fmap Just (readTQueue c) `orElse` return Nothing
113
158
-- @since 2.4.5
114
159
flushTQueue :: TQueue a -> STM [a ]
115
160
flushTQueue (TQueue read write) = do
116
- xs <- readTVar read
117
- ys <- readTVar write
118
- unless (null xs ) $ writeTVar read []
119
- unless (null ys ) $ writeTVar write []
120
- return (xs ++ reverse ys )
161
+ End front fsched <- readTVar read
162
+ End rear rsched <- readTVar write
163
+ unless (null front && null fsched ) $ writeTVar read ( End [] [] )
164
+ unless (null rear && null rsched ) $ writeTVar write ( End [] [] )
165
+ return (rotate front rear )
121
166
122
167
-- | Get the next value from the @TQueue@ without removing it,
123
168
-- retrying if the channel is empty.
124
169
peekTQueue :: TQueue a -> STM a
125
- peekTQueue c = do
126
- x <- readTQueue c
127
- unGetTQueue c x
128
- return x
170
+ peekTQueue (TQueue read _write) = do
171
+ End front _fsched <- readTVar read
172
+ case front of
173
+ x: _ -> return x
174
+ [] -> retry
129
175
130
176
-- | A version of 'peekTQueue' which does not retry. Instead it
131
177
-- returns @Nothing@ if no value is available.
132
178
tryPeekTQueue :: TQueue a -> STM (Maybe a )
133
- tryPeekTQueue c = do
134
- m <- tryReadTQueue c
135
- case m of
136
- Nothing -> return Nothing
137
- Just x -> do
138
- unGetTQueue c x
139
- return m
140
-
141
- -- | Put a data item back onto a channel, where it will be the next item read.
179
+ tryPeekTQueue (TQueue read _write) = do
180
+ End front _fsched <- readTVar read
181
+ case front of
182
+ x: _ -> return (Just x)
183
+ [] -> return Nothing
184
+
185
+ -- | Put a data item back onto a channel, where it will be the next item read.
142
186
unGetTQueue :: TQueue a -> a -> STM ()
143
187
unGetTQueue (TQueue read _write) a = do
144
- xs <- readTVar read
145
- writeTVar read (a : xs )
188
+ End front fsched <- readTVar read
189
+ writeTVar read (End (a : front) (a : a : fsched) )
146
190
147
- -- | Returns 'True' if the supplied 'TQueue' is empty.
191
+ -- | Returns 'True' if the supplied 'TQueue' is empty.
148
192
isEmptyTQueue :: TQueue a -> STM Bool
149
- isEmptyTQueue (TQueue read write) = do
150
- xs <- readTVar read
151
- case xs of
152
- (_: _) -> return False
153
- [] -> do ys <- readTVar write
154
- case ys of
155
- [] -> return True
156
- _ -> return False
193
+ isEmptyTQueue (TQueue read _write) = do
194
+ End front _fsched <- readTVar read
195
+ return $! null front
0 commit comments