blob: e1b9e1fe8979b397fac0c8b21e80592669177fb7 [file] [log] [blame]
Ben Claytoned01f2c2019-05-20 10:42:35 +01001// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
Ben Claytonb54838a2019-05-21 12:26:40 +010015// This file contains a number of synchronization primitives for concurrency.
16//
17// You may be tempted to change this code to unlock the mutex before calling
18// std::condition_variable::notify_[one,all]. Please read
19// https://issuetracker.google.com/issues/133135427 before making this sort of
20// change.
21
Ben Claytoned01f2c2019-05-20 10:42:35 +010022#ifndef sw_Synchronization_hpp
23#define sw_Synchronization_hpp
24
Ben Clayton6cd63a22019-05-20 11:06:46 +010025#include <assert.h>
Ben Clayton50c66902019-05-20 10:55:22 +010026#include <chrono>
Ben Claytoned01f2c2019-05-20 10:42:35 +010027#include <condition_variable>
28#include <mutex>
29#include <queue>
30
Nicolas Capens157ba262019-12-10 17:49:14 -050031namespace sw {
Ben Claytoned01f2c2019-05-20 10:42:35 +010032
Ben Clayton6d33e8c2019-05-20 11:15:03 +010033// TaskEvents is an interface for notifying when tasks begin and end.
34// Tasks can be nested and/or overlapping.
35// TaskEvents is used for task queue synchronization.
36class TaskEvents
37{
38public:
39 // start() is called before a task begins.
40 virtual void start() = 0;
41 // finish() is called after a task ends. finish() must only be called after
42 // a corresponding call to start().
43 virtual void finish() = 0;
44 // complete() is a helper for calling start() followed by finish().
45 inline void complete() { start(); finish(); }
Ben Claytoncaf60312019-05-21 15:31:12 +010046
47protected:
48 virtual ~TaskEvents() = default;
Ben Clayton6d33e8c2019-05-20 11:15:03 +010049};
50
Ben Clayton6cd63a22019-05-20 11:06:46 +010051// WaitGroup is a synchronization primitive that allows you to wait for
52// collection of asynchronous tasks to finish executing.
53// Call add() before each task begins, and then call done() when after each task
54// is finished.
55// At the same time, wait() can be used to block until all tasks have finished.
56// WaitGroup takes its name after Golang's sync.WaitGroup.
Ben Clayton6d33e8c2019-05-20 11:15:03 +010057class WaitGroup : public TaskEvents
Ben Clayton6cd63a22019-05-20 11:06:46 +010058{
59public:
60 // add() begins a new task.
61 void add()
62 {
63 std::unique_lock<std::mutex> lock(mutex);
64 ++count_;
65 }
66
67 // done() is called when a task of the WaitGroup has been completed.
68 // Returns true if there are no more tasks currently running in the
69 // WaitGroup.
70 bool done()
71 {
72 std::unique_lock<std::mutex> lock(mutex);
73 assert(count_ > 0);
74 --count_;
75 if(count_ == 0)
76 {
77 condition.notify_all();
78 }
79 return count_ == 0;
80 }
81
82 // wait() blocks until all the tasks have been finished.
83 void wait()
84 {
85 std::unique_lock<std::mutex> lock(mutex);
86 condition.wait(lock, [this] { return count_ == 0; });
87 }
88
89 // wait() blocks until all the tasks have been finished or the timeout
90 // has been reached, returning true if all tasks have been completed, or
91 // false if the timeout has been reached.
92 template <class CLOCK, class DURATION>
93 bool wait(const std::chrono::time_point<CLOCK, DURATION>& timeout)
94 {
95 std::unique_lock<std::mutex> lock(mutex);
96 return condition.wait_until(lock, timeout, [this] { return count_ == 0; });
97 }
98
99 // count() returns the number of times add() has been called without a call
100 // to done().
101 // Note: No lock is held after count() returns, so the count may immediately
102 // change after returning.
103 int32_t count()
104 {
105 std::unique_lock<std::mutex> lock(mutex);
106 return count_;
107 }
108
Ben Clayton6d33e8c2019-05-20 11:15:03 +0100109 // TaskEvents compliance
110 void start() override { add(); }
111 void finish() override { done(); }
112
Ben Clayton6cd63a22019-05-20 11:06:46 +0100113private:
114 int32_t count_ = 0; // guarded by mutex
115 std::mutex mutex;
116 std::condition_variable condition;
117};
118
Ben Claytoned01f2c2019-05-20 10:42:35 +0100119// Chan is a thread-safe FIFO queue of type T.
120// Chan takes its name after Golang's chan.
121template <typename T>
122class Chan
123{
124public:
125 Chan();
126
127 // take returns the next item in the chan, blocking until an item is
128 // available.
129 T take();
130
131 // tryTake returns a <T, bool> pair.
132 // If the chan is not empty, then the next item and true are returned.
133 // If the chan is empty, then a default-initialized T and false are returned.
134 std::pair<T, bool> tryTake();
135
136 // put places an item into the chan, blocking if the chan is bounded and
137 // full.
138 void put(const T &v);
139
140 // Returns the number of items in the chan.
141 // Note: that this may change as soon as the function returns, so should
142 // only be used for debugging.
143 size_t count();
144
145private:
146 std::queue<T> queue;
147 std::mutex mutex;
148 std::condition_variable added;
Ben Claytoned01f2c2019-05-20 10:42:35 +0100149};
150
151template <typename T>
152Chan<T>::Chan() {}
153
154template <typename T>
155T Chan<T>::take()
156{
157 std::unique_lock<std::mutex> lock(mutex);
Ben Clayton183b8ad2019-05-20 10:45:36 +0100158 // Wait for item to be added.
159 added.wait(lock, [this] { return queue.size() > 0; });
Ben Claytoned01f2c2019-05-20 10:42:35 +0100160 T out = queue.front();
161 queue.pop();
Ben Claytoned01f2c2019-05-20 10:42:35 +0100162 return out;
163}
164
165template <typename T>
166std::pair<T, bool> Chan<T>::tryTake()
167{
168 std::unique_lock<std::mutex> lock(mutex);
Nicolas Capens81bc9d92019-12-16 15:05:57 -0500169 if(queue.size() == 0)
Ben Claytoned01f2c2019-05-20 10:42:35 +0100170 {
171 return std::make_pair(T{}, false);
172 }
173 T out = queue.front();
174 queue.pop();
Ben Claytoned01f2c2019-05-20 10:42:35 +0100175 return std::make_pair(out, true);
176}
177
178template <typename T>
179void Chan<T>::put(const T &item)
180{
181 std::unique_lock<std::mutex> lock(mutex);
182 queue.push(item);
Ben Claytoned01f2c2019-05-20 10:42:35 +0100183 added.notify_one();
184}
185
186template <typename T>
187size_t Chan<T>::count()
188{
189 std::unique_lock<std::mutex> lock(mutex);
Ben Clayton183b8ad2019-05-20 10:45:36 +0100190 return queue.size();
Ben Claytoned01f2c2019-05-20 10:42:35 +0100191}
192
Nicolas Capens157ba262019-12-10 17:49:14 -0500193} // namespace sw
Ben Claytoned01f2c2019-05-20 10:42:35 +0100194
195#endif // sw_Synchronization_hpp