|
1
|
1 #include "pfc-lite.h"
|
|
|
2
|
|
|
3 #ifdef _WIN32
|
|
|
4 #include "pp-winapi.h"
|
|
|
5 #endif
|
|
|
6
|
|
|
7 #ifdef __APPLE__
|
|
|
8 #include <sys/sysctl.h>
|
|
|
9 #include <sys/stat.h>
|
|
|
10 #endif
|
|
|
11
|
|
|
12 #include "threads.h"
|
|
|
13 #include "debug.h"
|
|
|
14 #include <memory>
|
|
|
15
|
|
|
16 #include <thread>
|
|
|
17
|
|
|
18 #ifndef _WIN32
|
|
|
19 namespace {
|
|
|
20 class c_pthread_attr {
|
|
|
21 public:
|
|
|
22 c_pthread_attr() {
|
|
|
23 pthread_attr_init(&a);
|
|
|
24 }
|
|
|
25 ~c_pthread_attr() {
|
|
|
26 pthread_attr_destroy(&a);
|
|
|
27 }
|
|
|
28 void apply( const pfc::thread::arg_t & arg ) {
|
|
|
29 #ifdef __APPLE__
|
|
|
30 if ( arg.appleThreadQOS != 0 ) {
|
|
|
31 pthread_attr_set_qos_class_np( &a, (qos_class_t) arg.appleThreadQOS, arg.appleRealtivePriority );
|
|
|
32 }
|
|
|
33 #endif
|
|
|
34 if ( arg.nixSchedPolicy >= 0 ) {
|
|
|
35 pthread_attr_setschedpolicy(&a, arg.nixSchedPolicy);
|
|
|
36 pthread_attr_setschedparam(&a, &arg.nixSchedParam);
|
|
|
37 }
|
|
|
38 }
|
|
|
39 pthread_attr_t a;
|
|
|
40
|
|
|
41 c_pthread_attr( const c_pthread_attr & ) = delete;
|
|
|
42 void operator=( const c_pthread_attr & ) = delete;
|
|
|
43 };
|
|
|
44 }
|
|
|
45 #endif
|
|
|
46
|
|
|
47 namespace pfc {
|
|
|
48 unsigned getOptimalWorkerThreadCount() {
|
|
|
49 return std::thread::hardware_concurrency();
|
|
|
50 }
|
|
|
51
|
|
|
52 unsigned getOptimalWorkerThreadCountEx(size_t taskCountLimit) {
|
|
|
53 if (taskCountLimit <= 1) return 1;
|
|
|
54 return (unsigned)pfc::min_t<size_t>(taskCountLimit,getOptimalWorkerThreadCount());
|
|
|
55 }
|
|
|
56
|
|
|
57
|
|
|
58 void thread::entry() {
|
|
|
59 try {
|
|
|
60 threadProc();
|
|
|
61 } catch(...) {}
|
|
|
62 }
|
|
|
63
|
|
|
64 void thread::couldNotCreateThread() {
|
|
|
65 PFC_ASSERT(!"Could not create thread");
|
|
|
66 // Something terminally wrong, better crash leaving a good debug trace
|
|
|
67 crash();
|
|
|
68 }
|
|
|
69
|
|
|
70 #ifdef _WIN32
|
|
|
71 thread::thread() : m_thread(INVALID_HANDLE_VALUE)
|
|
|
72 {
|
|
|
73 }
|
|
|
74
|
|
|
75 void thread::close() {
|
|
|
76 if (isActive()) {
|
|
|
77
|
|
|
78 int ctxPriority = GetThreadPriority( GetCurrentThread() );
|
|
|
79 if (ctxPriority > GetThreadPriority( m_thread ) ) SetThreadPriority( m_thread, ctxPriority );
|
|
|
80
|
|
|
81 if (WaitForSingleObject(m_thread,INFINITE) != WAIT_OBJECT_0) couldNotCreateThread();
|
|
|
82 CloseHandle(m_thread); m_thread = INVALID_HANDLE_VALUE;
|
|
|
83 }
|
|
|
84 }
|
|
|
85 bool thread::isActive() const {
|
|
|
86 return m_thread != INVALID_HANDLE_VALUE;
|
|
|
87 }
|
|
|
88
|
|
|
89 static HANDLE MyBeginThread( unsigned (__stdcall * proc)(void *) , void * arg, DWORD * outThreadID, int priority) {
|
|
|
90 HANDLE thread;
|
|
|
91 #ifdef PFC_WINDOWS_DESKTOP_APP
|
|
|
92 thread = (HANDLE)_beginthreadex(NULL, 0, proc, arg, CREATE_SUSPENDED, (unsigned int*)outThreadID);
|
|
|
93 #else
|
|
|
94 thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)proc, arg, CREATE_SUSPENDED, outThreadID);
|
|
|
95 #endif
|
|
|
96 if (thread == NULL) thread::couldNotCreateThread();
|
|
|
97 SetThreadPriority(thread, priority);
|
|
|
98 ResumeThread(thread);
|
|
|
99 return thread;
|
|
|
100 }
|
|
|
101 void thread::winStart(int priority, DWORD * outThreadID) {
|
|
|
102 close();
|
|
|
103 m_thread = MyBeginThread(g_entry, reinterpret_cast<void*>(this), outThreadID, priority);
|
|
|
104 }
|
|
|
105 void thread::start(arg_t const & arg) {
|
|
|
106 winStart(arg.winThreadPriority, nullptr);
|
|
|
107 }
|
|
|
108
|
|
|
109 unsigned CALLBACK thread::g_entry(void* p_instance) {
|
|
|
110 reinterpret_cast<thread*>(p_instance)->entry(); return 0;
|
|
|
111 }
|
|
|
112
|
|
|
113 #else
|
|
|
114 thread::thread() : m_thread(), m_threadValid() {
|
|
|
115 }
|
|
|
116
|
|
|
117 #ifndef __APPLE__ // Apple specific entrypoint in obj-c.mm
|
|
|
118 void * thread::g_entry( void * arg ) {
|
|
|
119 reinterpret_cast<thread*>( arg )->entry();
|
|
|
120 return NULL;
|
|
|
121 }
|
|
|
122 #endif
|
|
|
123
|
|
|
124 void thread::start(arg_t const & arg) {
|
|
|
125 close();
|
|
|
126 #ifdef __APPLE__
|
|
|
127 appleStartThreadPrologue();
|
|
|
128 #endif
|
|
|
129 pthread_t thread;
|
|
|
130
|
|
|
131 c_pthread_attr attr;
|
|
|
132
|
|
|
133 pthread_attr_setdetachstate(&attr.a, PTHREAD_CREATE_JOINABLE);
|
|
|
134
|
|
|
135 attr.apply( arg );
|
|
|
136
|
|
|
137 if (pthread_create(&thread, &attr.a, g_entry, reinterpret_cast<void*>(this)) != 0) couldNotCreateThread();
|
|
|
138
|
|
|
139 m_threadValid = true;
|
|
|
140 m_thread = thread;
|
|
|
141 }
|
|
|
142
|
|
|
143 void thread::close() {
|
|
|
144 if (m_threadValid) {
|
|
|
145 void * rv = NULL;
|
|
|
146 pthread_join( m_thread, & rv ); m_thread = 0;
|
|
|
147 m_threadValid = false;
|
|
|
148 }
|
|
|
149 }
|
|
|
150
|
|
|
151 bool thread::isActive() const {
|
|
|
152 return m_threadValid;
|
|
|
153 }
|
|
|
154 #endif
|
|
|
155 #ifdef _WIN32
|
|
|
156 unsigned CALLBACK winSplitThreadProc(void* arg) {
|
|
|
157 auto f = reinterpret_cast<std::function<void() > *>(arg);
|
|
|
158 (*f)();
|
|
|
159 delete f;
|
|
|
160 return 0;
|
|
|
161 }
|
|
|
162 #else
|
|
|
163 void * nixSplitThreadProc(void * arg) {
|
|
|
164 auto f = reinterpret_cast<std::function<void() > *>(arg);
|
|
|
165 #ifdef __APPLE__
|
|
|
166 inAutoReleasePool([f] {
|
|
|
167 (*f)();
|
|
|
168 delete f;
|
|
|
169 });
|
|
|
170 #else
|
|
|
171 (*f)();
|
|
|
172 delete f;
|
|
|
173 #endif
|
|
|
174 return NULL;
|
|
|
175 }
|
|
|
176 #endif
|
|
|
177
|
|
|
178 void splitThread( std::function<void ()> f ) {
|
|
|
179 splitThread( thread::argCurrentThread(), f );
|
|
|
180 }
|
|
|
181
|
|
|
182 void splitThread(thread::arg_t const & arg, std::function<void() > f) {
|
|
|
183 auto arg2 = std::make_unique<std::function<void()> >(f);
|
|
|
184 #ifdef _WIN32
|
|
|
185 HANDLE h = MyBeginThread(winSplitThreadProc, arg2.get(), NULL, arg.winThreadPriority );
|
|
|
186 CloseHandle(h);
|
|
|
187 #else
|
|
|
188 #ifdef __APPLE__
|
|
|
189 thread::appleStartThreadPrologue();
|
|
|
190 #endif
|
|
|
191 pthread_t thread;
|
|
|
192
|
|
|
193 c_pthread_attr attr;
|
|
|
194
|
|
|
195 attr.apply( arg );
|
|
|
196
|
|
|
197 if (pthread_create(&thread, &attr.a, nixSplitThreadProc, arg2.get()) != 0) thread::couldNotCreateThread();
|
|
|
198
|
|
|
199 pthread_detach(thread);
|
|
|
200 #endif
|
|
|
201 arg2.release();
|
|
|
202 }
|
|
|
203 #ifndef __APPLE__
|
|
|
204 // Stub for non Apple
|
|
|
205 void inAutoReleasePool(std::function<void()> f) { f(); }
|
|
|
206 #endif
|
|
|
207
|
|
|
208
|
|
|
209 void thread2::startHere(std::function<void()> e) {
|
|
|
210 setEntry(e); start();
|
|
|
211 }
|
|
|
212 void thread2::startHere(arg_t const& arg, std::function<void()> e) {
|
|
|
213 setEntry(e); start(arg);
|
|
|
214 }
|
|
|
215
|
|
|
216 void thread2::setEntry(std::function<void()> e) {
|
|
|
217 PFC_ASSERT(!isActive());
|
|
|
218 m_entryPoint = e;
|
|
|
219 }
|
|
|
220
|
|
|
221 void thread2::threadProc() {
|
|
|
222 m_entryPoint();
|
|
|
223 }
|
|
|
224
|
|
|
225 thread::handle_t thread::handleToCurrent() {
|
|
|
226 #ifdef _WIN32
|
|
|
227 return GetCurrentThread();
|
|
|
228 #else
|
|
|
229 return pthread_self();
|
|
|
230 #endif
|
|
|
231 }
|
|
|
232
|
|
|
233 thread::arg_t thread::argDefault() {
|
|
|
234 return arg_t();
|
|
|
235 }
|
|
|
236
|
|
|
237 thread::arg_t thread::argCurrentThread() {
|
|
|
238 arg_t arg;
|
|
|
239 #ifdef _WIN32
|
|
|
240 arg.winThreadPriority = GetThreadPriority( GetCurrentThread() );
|
|
|
241 #endif
|
|
|
242 #ifdef __APPLE__
|
|
|
243 qos_class_t qos_class = QOS_CLASS_UNSPECIFIED;
|
|
|
244 int relative_priority = 0;
|
|
|
245 if (pthread_get_qos_class_np(pthread_self(), &qos_class, &relative_priority) == 0) {
|
|
|
246 arg.appleThreadQOS = (int) qos_class;
|
|
|
247 arg.appleRealtivePriority = relative_priority;
|
|
|
248 }
|
|
|
249 #endif
|
|
|
250 #ifndef _WIN32
|
|
|
251 {
|
|
|
252 int policy; sched_param param;
|
|
|
253 if (pthread_getschedparam( pthread_self(), &policy, ¶m) == 0) {
|
|
|
254 arg.nixSchedPolicy = policy;
|
|
|
255 arg.nixSchedParam = param;
|
|
|
256 }
|
|
|
257 }
|
|
|
258 #endif
|
|
|
259 return arg;
|
|
|
260 }
|
|
|
261
|
|
|
262 thread::arg_t thread::argBackground() {
|
|
|
263 #ifdef _WIN32
|
|
|
264 return argWinPriority(THREAD_PRIORITY_LOWEST);
|
|
|
265 #elif defined(__APPLE__)
|
|
|
266 return argAppleQOS((int)QOS_CLASS_BACKGROUND);
|
|
|
267 #else
|
|
|
268 return argNixPriority(SCHED_OTHER, 0);
|
|
|
269 #endif
|
|
|
270 }
|
|
|
271
|
|
|
272 thread::arg_t thread::argHighPriority() {
|
|
|
273 #ifdef _WIN32
|
|
|
274 return argWinPriority(THREAD_PRIORITY_HIGHEST);
|
|
|
275 #elif defined(__ANDROID__)
|
|
|
276 // No SCHED_FIFO or SCHED_RR on Android
|
|
|
277 return argNixPriority(SCHED_OTHER, 100);
|
|
|
278 #else
|
|
|
279 return argNixPriority(SCHED_RR, 75);
|
|
|
280 #endif
|
|
|
281 }
|
|
|
282
|
|
|
283 thread::arg_t thread::argPlayback() {
|
|
|
284 // It would be nice to use realtime priority yet lock ourselves to the slow cores, but that just doesn't work
|
|
|
285 // Using priority overrides the request for the slow cores (QOS_CLASS_BACKGROUND)
|
|
|
286 // ret.appleThreadQOS = (int) QOS_CLASS_BACKGROUND;
|
|
|
287
|
|
|
288 #ifdef _WIN32
|
|
|
289 return argWinPriority(THREAD_PRIORITY_TIME_CRITICAL);
|
|
|
290 #elif defined(__ANDROID__)
|
|
|
291 // No SCHED_FIFO or SCHED_RR on Android
|
|
|
292 return argNixPriority(SCHED_OTHER, 100);
|
|
|
293 #else
|
|
|
294 return argNixPriority(SCHED_FIFO, 100);
|
|
|
295 #endif
|
|
|
296
|
|
|
297 }
|
|
|
298
|
|
|
299 thread::arg_t thread::argUserInitiated() {
|
|
|
300 #ifdef _WIN32
|
|
|
301 return argWinPriority(THREAD_PRIORITY_BELOW_NORMAL);
|
|
|
302 #elif defined(__APPLE__)
|
|
|
303 return argAppleQOS((int) QOS_CLASS_USER_INITIATED);
|
|
|
304 #else
|
|
|
305 return argNixPriority(SCHED_OTHER, 25);
|
|
|
306 #endif
|
|
|
307 }
|
|
|
308
|
|
|
309 #ifdef _WIN32
|
|
|
310 thread::arg_t thread::argWinPriority(int priority) {
|
|
|
311 arg_t arg;
|
|
|
312 arg.winThreadPriority = priority;
|
|
|
313 return arg;
|
|
|
314 }
|
|
|
315 #endif
|
|
|
316 #ifdef __APPLE__
|
|
|
317 thread::arg_t thread::argAppleQOS(int qos) {
|
|
|
318 arg_t arg;
|
|
|
319 arg.appleThreadQOS = qos;
|
|
|
320 return arg;
|
|
|
321 }
|
|
|
322 #endif
|
|
|
323 #ifndef _WIN32
|
|
|
324 thread::arg_t thread::argNixPriority(int policy, int percent) {
|
|
|
325 int pval;
|
|
|
326 if ( percent <= 0 ) pval = sched_get_priority_min(policy);
|
|
|
327 else if ( percent >= 100 ) pval = sched_get_priority_max(policy);
|
|
|
328 else {
|
|
|
329 const int pmin = sched_get_priority_min(policy), pmax = sched_get_priority_max(policy);
|
|
|
330 if (pmax > pmin) {
|
|
|
331 pval = pmin + ((pmax - pmin) * percent / 100);
|
|
|
332 } else {
|
|
|
333 pval = pmin;
|
|
|
334 }
|
|
|
335 }
|
|
|
336 arg_t ret;
|
|
|
337 ret.nixSchedPolicy = policy;
|
|
|
338 ret.nixSchedParam = sched_param { pval };
|
|
|
339 return ret;
|
|
|
340 }
|
|
|
341 #endif
|
|
|
342 void thread::setCurrentPriority(arg_t const& arg) {
|
|
|
343 #ifdef _WIN32
|
|
|
344 ::SetThreadPriority(GetCurrentThread(), arg.winThreadPriority);
|
|
|
345 #else
|
|
|
346 #ifdef __APPLE__
|
|
|
347 if (arg.appleThreadQOS != 0) {
|
|
|
348 pthread_set_qos_class_self_np((qos_class_t)arg.appleThreadQOS, arg.appleRealtivePriority);
|
|
|
349 }
|
|
|
350
|
|
|
351 #endif
|
|
|
352 if (arg.nixSchedPolicy >= 0) {
|
|
|
353 pthread_setschedparam(pthread_self(), arg.nixSchedPolicy, &arg.nixSchedParam);
|
|
|
354 }
|
|
|
355 #endif
|
|
|
356 }
|
|
|
357
|
|
|
358 }
|