blob: 0a5068d2a341039789686e125f6feb49684bbe57 [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().
Ben Clayton595d9112019-12-17 20:37:57 +000045 inline void complete()
46 {
47 start();
48 finish();
49 }
Ben Claytoncaf60312019-05-21 15:31:12 +010050
51protected:
52 virtual ~TaskEvents() = default;
Ben Clayton6d33e8c2019-05-20 11:15:03 +010053};
54
Ben Clayton6cd63a22019-05-20 11:06:46 +010055// WaitGroup is a synchronization primitive that allows you to wait for
56// collection of asynchronous tasks to finish executing.
57// Call add() before each task begins, and then call done() when after each task
58// is finished.
59// At the same time, wait() can be used to block until all tasks have finished.
60// WaitGroup takes its name after Golang's sync.WaitGroup.
Ben Clayton6d33e8c2019-05-20 11:15:03 +010061class WaitGroup : public TaskEvents
Ben Clayton6cd63a22019-05-20 11:06:46 +010062{
63public:
64 // add() begins a new task.
65 void add()
66 {
67 std::unique_lock<std::mutex> lock(mutex);
68 ++count_;
69 }
70
71 // done() is called when a task of the WaitGroup has been completed.
72 // Returns true if there are no more tasks currently running in the
73 // WaitGroup.
74 bool done()
75 {
76 std::unique_lock<std::mutex> lock(mutex);
77 assert(count_ > 0);
78 --count_;
79 if(count_ == 0)
80 {
81 condition.notify_all();
82 }
83 return count_ == 0;
84 }
85
86 // wait() blocks until all the tasks have been finished.
87 void wait()
88 {
89 std::unique_lock<std::mutex> lock(mutex);
90 condition.wait(lock, [this] { return count_ == 0; });
91 }
92
93 // wait() blocks until all the tasks have been finished or the timeout
94 // has been reached, returning true if all tasks have been completed, or
95 // false if the timeout has been reached.
Ben Clayton595d9112019-12-17 20:37:57 +000096 template<class CLOCK, class DURATION>
97 bool wait(const std::chrono::time_point<CLOCK, DURATION> &timeout)
Ben Clayton6cd63a22019-05-20 11:06:46 +010098 {
99 std::unique_lock<std::mutex> lock(mutex);
100 return condition.wait_until(lock, timeout, [this] { return count_ == 0; });
101 }
102
103 // count() returns the number of times add() has been called without a call
104 // to done().
105 // Note: No lock is held after count() returns, so the count may immediately
106 // change after returning.
107 int32_t count()
108 {
109 std::unique_lock<std::mutex> lock(mutex);
110 return count_;
111 }
112
Ben Clayton6d33e8c2019-05-20 11:15:03 +0100113 // TaskEvents compliance
114 void start() override { add(); }
115 void finish() override { done(); }
116
Ben Clayton6cd63a22019-05-20 11:06:46 +0100117private:
Ben Clayton595d9112019-12-17 20:37:57 +0000118 int32_t count_ = 0; // guarded by mutex
Ben Clayton6cd63a22019-05-20 11:06:46 +0100119 std::mutex mutex;
120 std::condition_variable condition;
121};
122
Ben Claytoned01f2c2019-05-20 10:42:35 +0100123// Chan is a thread-safe FIFO queue of type T.
124// Chan takes its name after Golang's chan.
Ben Clayton595d9112019-12-17 20:37:57 +0000125template<typename T>
Ben Claytoned01f2c2019-05-20 10:42:35 +0100126class Chan
127{
128public:
129 Chan();
130
131 // take returns the next item in the chan, blocking until an item is
132 // available.
133 T take();
134
135 // tryTake returns a <T, bool> pair.
136 // If the chan is not empty, then the next item and true are returned.
137 // If the chan is empty, then a default-initialized T and false are returned.
138 std::pair<T, bool> tryTake();
139
140 // put places an item into the chan, blocking if the chan is bounded and
141 // full.
142 void put(const T &v);
143
144 // Returns the number of items in the chan.
145 // Note: that this may change as soon as the function returns, so should
146 // only be used for debugging.
147 size_t count();
148
149private:
150 std::queue<T> queue;
151 std::mutex mutex;
152 std::condition_variable added;
Ben Claytoned01f2c2019-05-20 10:42:35 +0100153};
154
Ben Clayton595d9112019-12-17 20:37:57 +0000155template<typename T>
156Chan<T>::Chan()
157{}
Ben Claytoned01f2c2019-05-20 10:42:35 +0100158
Ben Clayton595d9112019-12-17 20:37:57 +0000159template<typename T>
Ben Claytoned01f2c2019-05-20 10:42:35 +0100160T Chan<T>::take()
161{
162 std::unique_lock<std::mutex> lock(mutex);
Ben Clayton183b8ad2019-05-20 10:45:36 +0100163 // Wait for item to be added.
164 added.wait(lock, [this] { return queue.size() > 0; });
Ben Claytoned01f2c2019-05-20 10:42:35 +0100165 T out = queue.front();
166 queue.pop();
Ben Claytoned01f2c2019-05-20 10:42:35 +0100167 return out;
168}
169
Ben Clayton595d9112019-12-17 20:37:57 +0000170template<typename T>
Ben Claytoned01f2c2019-05-20 10:42:35 +0100171std::pair<T, bool> Chan<T>::tryTake()
172{
173 std::unique_lock<std::mutex> lock(mutex);
Nicolas Capens81bc9d92019-12-16 15:05:57 -0500174 if(queue.size() == 0)
Ben Claytoned01f2c2019-05-20 10:42:35 +0100175 {
176 return std::make_pair(T{}, false);
177 }
178 T out = queue.front();
179 queue.pop();
Ben Claytoned01f2c2019-05-20 10:42:35 +0100180 return std::make_pair(out, true);
181}
182
Ben Clayton595d9112019-12-17 20:37:57 +0000183template<typename T>
Ben Claytoned01f2c2019-05-20 10:42:35 +0100184void Chan<T>::put(const T &item)
185{
186 std::unique_lock<std::mutex> lock(mutex);
187 queue.push(item);
Ben Claytoned01f2c2019-05-20 10:42:35 +0100188 added.notify_one();
189}
190
Ben Clayton595d9112019-12-17 20:37:57 +0000191template<typename T>
Ben Claytoned01f2c2019-05-20 10:42:35 +0100192size_t Chan<T>::count()
193{
194 std::unique_lock<std::mutex> lock(mutex);
Ben Clayton183b8ad2019-05-20 10:45:36 +0100195 return queue.size();
Ben Claytoned01f2c2019-05-20 10:42:35 +0100196}
197
Nicolas Capens157ba262019-12-10 17:49:14 -0500198} // namespace sw
Ben Claytoned01f2c2019-05-20 10:42:35 +0100199
Ben Clayton595d9112019-12-17 20:37:57 +0000200#endif // sw_Synchronization_hpp