comparison foosdk/sdk/pfc/threads.cpp @ 1:20d02a178406 default tip

*: check in everything else yay
author Paper <paper@tflc.us>
date Mon, 05 Jan 2026 02:15:46 -0500
parents
children
comparison
equal deleted inserted replaced
0:e9bb126753e7 1:20d02a178406
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, &param) == 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 }