1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

Thread: Introduce a new Thread backend

This is a breaking change - see BREAKING-CHANGES.txt
This commit is contained in:
chroma 2022-10-18 11:47:18 +01:00
parent 621e14d092
commit d3cff375be
39 changed files with 849 additions and 603 deletions

View file

@ -1,9 +1,37 @@
JUCE breaking changes JUCE breaking changes
===================== =====================
develop Develop
======= =======
Change
------
The Thread::startThread (int) and Thread::setPriority (int) methods have been
removed. A new Thread priority API has been introduced.
Possible Issues
---------------
Code will fail to compile.
Workaround
----------
Rather than using an integer thread priority you must instead use a value from
the Thread::Priority enum. Thread::setPriority and Thread::getPriority should
only be called from the target thread. To start a Thread with a realtime
performance profile you must call startRealtimeThread.
Rationale
---------
Operating systems are moving away from a specific thread priority and towards
more granular control over which types of cores can be used and things like
power throttling options. In particular, it is no longer possible to map a 0-10
integer to a meaningful performance range on macOS ARM using the pthread
interface. Using a more modern interface grants us access to more runtime
options, but also changes how we can work with threads. The two most
significant changes are that we cannot mix operations using the new and old
interfaces, and that changing a priority using the new interface can only be
done on the currently running thread.
Change Change
------ ------
The constructor of WebBrowserComponent now requires passing in an instance of The constructor of WebBrowserComponent now requires passing in an instance of
@ -40,7 +68,6 @@ browser backend is more intuitive then requiring the user to derive from a
special class, especially if additional browser backends are added in the special class, especially if additional browser backends are added in the
future. future.
Change Change
------ ------
The function AudioIODeviceCallback::audioDeviceIOCallback() was removed. The function AudioIODeviceCallback::audioDeviceIOCallback() was removed.

View file

@ -325,7 +325,7 @@ public:
// audio setup // audio setup
formatManager.registerBasicFormats(); formatManager.registerBasicFormats();
thread.startThread (3); thread.startThread (Thread::Priority::normal);
#ifndef JUCE_DEMO_RUNNER #ifndef JUCE_DEMO_RUNNER
RuntimePermissions::request (RuntimePermissions::recordAudio, RuntimePermissions::request (RuntimePermissions::recordAudio,

View file

@ -57,7 +57,7 @@ public:
{ {
setOpaque (true); setOpaque (true);
imageList.setDirectory (File::getSpecialLocation (File::userPicturesDirectory), true, true); imageList.setDirectory (File::getSpecialLocation (File::userPicturesDirectory), true, true);
directoryThread.startThread (1); directoryThread.startThread (Thread::Priority::background);
fileTree.setTitle ("Files"); fileTree.setTitle ("Files");
fileTree.addListener (this); fileTree.addListener (this);

View file

@ -154,7 +154,7 @@ public:
setOpaque (true); setOpaque (true);
movieList.setDirectory (File::getSpecialLocation (File::userMoviesDirectory), true, true); movieList.setDirectory (File::getSpecialLocation (File::userMoviesDirectory), true, true);
directoryThread.startThread (1); directoryThread.startThread (Thread::Priority::background);
fileTree.setTitle ("Files"); fileTree.setTitle ("Files");
fileTree.addListener (this); fileTree.addListener (this);

View file

@ -63,7 +63,7 @@ public:
SharedTimeSliceThread() SharedTimeSliceThread()
: TimeSliceThread (String (JucePlugin_Name) + " ARA Sample Reading Thread") : TimeSliceThread (String (JucePlugin_Name) + " ARA Sample Reading Thread")
{ {
startThread (7); // Above default priority so playback is fluent, but below realtime startThread (Priority::high); // Above default priority so playback is fluent, but below realtime
} }
}; };

View file

@ -148,9 +148,7 @@ public:
: BouncingBall (containerComp), : BouncingBall (containerComp),
Thread ("JUCE Demo Thread") Thread ("JUCE Demo Thread")
{ {
// give the threads a random priority, so some will move more startThread();
// smoothly than others..
startThread (Random::getSystemRandom().nextInt (3) + 3);
} }
~DemoThread() override ~DemoThread() override

View file

@ -44,7 +44,7 @@ void LatestVersionCheckerAndUpdater::checkForNewVersion (bool background)
if (! isThreadRunning()) if (! isThreadRunning())
{ {
backgroundCheck = background; backgroundCheck = background;
startThread (3); startThread (Priority::low);
} }
} }
@ -373,7 +373,7 @@ public:
: ThreadWithProgressWindow ("Downloading New Version", true, true), : ThreadWithProgressWindow ("Downloading New Version", true, true),
asset (a), targetFolder (t), completionCallback (std::move (cb)) asset (a), targetFolder (t), completionCallback (std::move (cb))
{ {
launchThread (3); launchThread (Priority::low);
} }
private: private:

View file

@ -87,7 +87,7 @@ void MidiOutput::clearAllPendingMessages()
void MidiOutput::startBackgroundThread() void MidiOutput::startBackgroundThread()
{ {
startThread (9); startThread (Priority::high);
} }
void MidiOutput::stopBackgroundThread() void MidiOutput::stopBackgroundThread()

View file

@ -253,7 +253,7 @@ public:
if (inputDevice != nullptr) if (inputDevice != nullptr)
env->CallVoidMethod (inputDevice, AudioRecord.startRecording); env->CallVoidMethod (inputDevice, AudioRecord.startRecording);
startThread (8); startThread (Priority::high);
} }
else else
{ {

View file

@ -618,7 +618,7 @@ public:
if (outputDevice != nullptr && JUCE_ALSA_FAILED (snd_pcm_prepare (outputDevice->handle))) if (outputDevice != nullptr && JUCE_ALSA_FAILED (snd_pcm_prepare (outputDevice->handle)))
return; return;
startThread (9); startThread (Priority::high);
int count = 1000; int count = 1000;

View file

@ -1196,7 +1196,7 @@ String DSoundAudioIODevice::openDevice (const BigInteger& inputChannels,
for (int i = 0; i < inChans.size(); ++i) for (int i = 0; i < inChans.size(); ++i)
inChans.getUnchecked(i)->synchronisePosition(); inChans.getUnchecked(i)->synchronisePosition();
startThread (9); startThread (Priority::highest);
sleep (10); sleep (10);
notify(); notify();

View file

@ -1387,7 +1387,7 @@ public:
shouldShutdown = false; shouldShutdown = false;
deviceSampleRateChanged = false; deviceSampleRateChanged = false;
startThread (8); startThread (Priority::high);
Thread::sleep (5); Thread::sleep (5);
if (inputDevice != nullptr && inputDevice->client != nullptr) if (inputDevice != nullptr && inputDevice->client != nullptr)

View file

@ -254,7 +254,7 @@ public:
void runTest() override void runTest() override
{ {
TimeSliceThread timeSlice ("TestBackgroundThread"); TimeSliceThread timeSlice ("TestBackgroundThread");
timeSlice.startThread (5); timeSlice.startThread (Thread::Priority::normal);
beginTest ("Timeout"); beginTest ("Timeout");
{ {

View file

@ -25,8 +25,6 @@
#if JUCE_LINUX || JUCE_BSD #if JUCE_LINUX || JUCE_BSD
#include <thread>
namespace juce namespace juce
{ {
@ -34,15 +32,15 @@ namespace juce
bool dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages); bool dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages);
/** @internal */ /** @internal */
class MessageThread class MessageThread : public Thread
{ {
public: public:
MessageThread() MessageThread() : Thread ("JUCE Plugin Message Thread")
{ {
start(); start();
} }
~MessageThread() ~MessageThread() override
{ {
MessageManager::getInstance()->stopDispatchLoop(); MessageManager::getInstance()->stopDispatchLoop();
stop(); stop();
@ -50,51 +48,37 @@ public:
void start() void start()
{ {
if (isRunning()) startThread (Priority::high);
stop();
shouldExit = false; // Wait for setCurrentThreadAsMessageThread() and getInstance to be executed
// before leaving this method
threadInitialised.wait (10000);
}
thread = std::thread { [this] void stop()
{ {
Thread::setCurrentThreadPriority (7); signalThreadShouldExit();
Thread::setCurrentThreadName ("JUCE Plugin Message Thread"); stopThread (-1);
}
bool isRunning() const noexcept { return isThreadRunning(); }
void run() override
{
MessageManager::getInstance()->setCurrentThreadAsMessageThread(); MessageManager::getInstance()->setCurrentThreadAsMessageThread();
XWindowSystem::getInstance(); XWindowSystem::getInstance();
threadInitialised.signal(); threadInitialised.signal();
for (;;) while (! threadShouldExit())
{ {
if (! dispatchNextMessageOnSystemQueue (true)) if (! dispatchNextMessageOnSystemQueue (true))
Thread::sleep (1); Thread::sleep (1);
if (shouldExit)
break;
} }
} };
threadInitialised.wait();
} }
void stop()
{
if (! isRunning())
return;
shouldExit = true;
thread.join();
}
bool isRunning() const noexcept { return thread.joinable(); }
private: private:
WaitableEvent threadInitialised; WaitableEvent threadInitialised;
std::thread thread;
std::atomic<bool> shouldExit { false };
JUCE_DECLARE_NON_MOVEABLE (MessageThread) JUCE_DECLARE_NON_MOVEABLE (MessageThread)
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessageThread) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessageThread)
}; };

View file

@ -64,7 +64,7 @@ AudioThumbnailCache::AudioThumbnailCache (const int maxNumThumbs)
maxNumThumbsToStore (maxNumThumbs) maxNumThumbsToStore (maxNumThumbs)
{ {
jassert (maxNumThumbsToStore > 0); jassert (maxNumThumbsToStore > 0);
thread.startThread (2); thread.startThread (Thread::Priority::low);
} }
AudioThumbnailCache::~AudioThumbnailCache() AudioThumbnailCache::~AudioThumbnailCache()

View file

@ -186,6 +186,7 @@
#include "zip/juce_ZipFile.cpp" #include "zip/juce_ZipFile.cpp"
#include "files/juce_FileFilter.cpp" #include "files/juce_FileFilter.cpp"
#include "files/juce_WildcardFileFilter.cpp" #include "files/juce_WildcardFileFilter.cpp"
#include "native/juce_native_ThreadPriorities.h"
//============================================================================== //==============================================================================
#if ! JUCE_WINDOWS #if ! JUCE_WINDOWS

View file

@ -203,6 +203,7 @@
#include <sys/ptrace.h> #include <sys/ptrace.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/sysinfo.h> #include <sys/sysinfo.h>
#include <sys/time.h> #include <sys/time.h>
#include <sys/types.h> #include <sys/types.h>

View file

@ -344,36 +344,79 @@ LocalRef<jobject> getMainActivity() noexcept
} }
//============================================================================== //==============================================================================
// sets the process to 0=low priority, 1=normal, 2=high, 3=realtime #if JUCE_ANDROID && JUCE_MODULE_AVAILABLE_juce_audio_devices && (JUCE_USE_ANDROID_OPENSLES || JUCE_USE_ANDROID_OBOE)
JUCE_API void JUCE_CALLTYPE Process::setPriority (ProcessPriority prior) #define JUCE_ANDROID_REALTIME_THREAD_AVAILABLE 1
#endif
#if JUCE_ANDROID_REALTIME_THREAD_AVAILABLE
pthread_t juce_createRealtimeAudioThread (void* (*entry) (void*), void* userPtr);
#endif
extern JavaVM* androidJNIJavaVM;
bool Thread::createNativeThread (Priority)
{ {
// TODO if (isRealtime())
{
struct sched_param param; #if JUCE_ANDROID_REALTIME_THREAD_AVAILABLE
int policy, maxp, minp; threadHandle = (void*) juce_createRealtimeAudioThread (threadEntryProc, this);
threadId = (ThreadID) threadHandle.get();
const int p = (int) prior; return threadId != nullptr;
#else
if (p <= 1) jassertfalse;
policy = SCHED_OTHER; #endif
else
policy = SCHED_RR;
minp = sched_get_priority_min (policy);
maxp = sched_get_priority_max (policy);
if (p < 2)
param.sched_priority = 0;
else if (p == 2 )
// Set to middle of lower realtime priority range
param.sched_priority = minp + (maxp - minp) / 4;
else
// Set to middle of higher realtime priority range
param.sched_priority = minp + (3 * (maxp - minp) / 4);
pthread_setschedparam (pthread_self(), policy, &param);
} }
PosixThreadAttribute attr { threadStackSize };
threadId = threadHandle = makeThreadHandle (attr, this, [] (void* userData) -> void*
{
auto* myself = static_cast<Thread*> (userData);
juce_threadEntryPoint (myself);
if (androidJNIJavaVM != nullptr)
{
void* env = nullptr;
androidJNIJavaVM->GetEnv (&env, JNI_VERSION_1_2);
// only detach if we have actually been attached
if (env != nullptr)
androidJNIJavaVM->DetachCurrentThread();
}
return nullptr;
});
return threadId != nullptr;
}
void Thread::killThread()
{
if (threadHandle != nullptr)
jassertfalse; // pthread_cancel not available!
}
Thread::Priority Thread::getPriority() const
{
jassert (Thread::getCurrentThreadId() == getThreadId());
const auto native = getpriority (PRIO_PROCESS, (id_t) gettid());
return ThreadPriorities::getJucePriority (native);
}
bool Thread::setPriority (Priority)
{
jassert (Thread::getCurrentThreadId() == getThreadId());
if (isRealtime())
return false;
return setpriority (PRIO_PROCESS, (id_t) gettid(), ThreadPriorities::getNativePriority (priority)) == 0;
}
//==============================================================================
JUCE_API void JUCE_CALLTYPE Process::setPriority (ProcessPriority) {}
JUCE_API bool JUCE_CALLTYPE juce_isRunningUnderDebugger() noexcept JUCE_API bool JUCE_CALLTYPE juce_isRunningUnderDebugger() noexcept
{ {
StringArray lines; StringArray lines;

View file

@ -28,26 +28,48 @@ namespace juce
live in juce_posix_SharedCode.h! live in juce_posix_SharedCode.h!
*/ */
bool Thread::createNativeThread (Priority)
{
PosixThreadAttribute attr { threadStackSize };
PosixSchedulerPriority::getNativeSchedulerAndPriority (realtimeOptions, {}).apply (attr);
threadId = threadHandle = makeThreadHandle (attr, this, [] (void* userData) -> void*
{
auto* myself = static_cast<Thread*> (userData);
juce_threadEntryPoint (myself);
return nullptr;
});
return threadId != nullptr;
}
void Thread::killThread()
{
if (threadHandle != nullptr)
pthread_cancel ((pthread_t) threadHandle.load());
}
// Until we implement Nice awareness, these don't do anything on Linux.
Thread::Priority Thread::getPriority() const
{
jassert (Thread::getCurrentThreadId() == getThreadId());
return priority;
}
bool Thread::setPriority (Priority newPriority)
{
jassert (Thread::getCurrentThreadId() == getThreadId());
// Return true to make it compatible with other platforms.
priority = newPriority;
return true;
}
//============================================================================== //==============================================================================
JUCE_API void JUCE_CALLTYPE Process::setPriority (const ProcessPriority prior) JUCE_API void JUCE_CALLTYPE Process::setPriority (ProcessPriority) {}
{
auto policy = (prior <= NormalPriority) ? SCHED_OTHER : SCHED_RR;
auto minp = sched_get_priority_min (policy);
auto maxp = sched_get_priority_max (policy);
struct sched_param param;
switch (prior)
{
case LowPriority:
case NormalPriority: param.sched_priority = 0; break;
case HighPriority: param.sched_priority = minp + (maxp - minp) / 4; break;
case RealtimePriority: param.sched_priority = minp + (3 * (maxp - minp) / 4); break;
default: jassertfalse; break;
}
pthread_setschedparam (pthread_self(), policy, &param);
}
static bool swapUserAndEffectiveUser() static bool swapUserAndEffectiveUser()
{ {

View file

@ -32,6 +32,136 @@ namespace juce
bool isIOSAppActive = true; bool isIOSAppActive = true;
#endif #endif
API_AVAILABLE (macos (10.10))
static auto getNativeQOS (Thread::Priority priority)
{
switch (priority)
{
case Thread::Priority::highest: return QOS_CLASS_USER_INTERACTIVE;
case Thread::Priority::high: return QOS_CLASS_USER_INITIATED;
case Thread::Priority::low: return QOS_CLASS_UTILITY;
case Thread::Priority::background: return QOS_CLASS_BACKGROUND;
case Thread::Priority::normal: break;
}
return QOS_CLASS_DEFAULT;
}
API_AVAILABLE (macos (10.10))
static auto getJucePriority (qos_class_t qos)
{
switch (qos)
{
case QOS_CLASS_USER_INTERACTIVE: return Thread::Priority::highest;
case QOS_CLASS_USER_INITIATED: return Thread::Priority::high;
case QOS_CLASS_UTILITY: return Thread::Priority::low;
case QOS_CLASS_BACKGROUND: return Thread::Priority::background;
case QOS_CLASS_UNSPECIFIED:
case QOS_CLASS_DEFAULT: break;
}
return Thread::Priority::normal;
}
bool Thread::createNativeThread (Priority priority)
{
PosixThreadAttribute attr { threadStackSize };
if (@available (macos 10.10, *))
pthread_attr_set_qos_class_np (attr.get(), getNativeQOS (priority), 0);
else
PosixSchedulerPriority::getNativeSchedulerAndPriority (realtimeOptions, priority).apply (attr);
threadId = threadHandle = makeThreadHandle (attr, this, [] (void* userData) -> void*
{
auto* myself = static_cast<Thread*> (userData);
JUCE_AUTORELEASEPOOL
{
juce_threadEntryPoint (myself);
}
return nullptr;
});
return threadId != nullptr;
}
void Thread::killThread()
{
if (threadHandle != nullptr)
pthread_cancel ((pthread_t) threadHandle.load());
}
Thread::Priority Thread::getPriority() const
{
jassert (Thread::getCurrentThreadId() == getThreadId());
if (! isRealtime())
{
if (@available (macOS 10.10, *))
return getJucePriority (qos_class_self());
// fallback for older versions of macOS
const auto min = jmax (0, sched_get_priority_min (SCHED_OTHER));
const auto max = jmax (0, sched_get_priority_max (SCHED_OTHER));
if (min != 0 && max != 0)
{
const auto native = PosixSchedulerPriority::findCurrentSchedulerAndPriority().getPriority();
const auto mapped = jmap (native, min, max, 0, 4);
return ThreadPriorities::getJucePriority (mapped);
}
}
return {};
}
bool Thread::setPriority (Priority priority)
{
jassert (Thread::getCurrentThreadId() == getThreadId());
if (isRealtime())
{
// macOS/iOS needs to know how much time you need!
jassert (realtimeOptions->workDurationMs > 0);
mach_timebase_info_data_t timebase;
mach_timebase_info (&timebase);
const auto periodMs = realtimeOptions->workDurationMs;
const auto ticksPerMs = ((double) timebase.denom * 1000000.0) / (double) timebase.numer;
const auto periodTicks = (uint32_t) jmin ((double) std::numeric_limits<uint32_t>::max(), periodMs * ticksPerMs);
thread_time_constraint_policy_data_t policy;
policy.period = periodTicks;
policy.computation = jmin ((uint32_t) 50000, policy.period);
policy.constraint = policy.period;
policy.preemptible = true;
return thread_policy_set (pthread_mach_thread_np (pthread_self()),
THREAD_TIME_CONSTRAINT_POLICY,
(thread_policy_t) &policy,
THREAD_TIME_CONSTRAINT_POLICY_COUNT) == KERN_SUCCESS;
}
if (@available (macOS 10.10, *))
return pthread_set_qos_class_self_np (getNativeQOS (priority), 0) == 0;
#if JUCE_ARM
// M1 platforms should never reach this code!!!!!!
jassertfalse;
#endif
// Just in case older versions of macOS support SCHED_OTHER priorities.
const auto psp = PosixSchedulerPriority::getNativeSchedulerAndPriority ({}, priority);
struct sched_param param;
param.sched_priority = psp.getPriority();
return pthread_setschedparam (pthread_self(), psp.getScheduler(), &param) == 0;
}
//============================================================================== //==============================================================================
JUCE_API bool JUCE_CALLTYPE Process::isForegroundProcess() JUCE_API bool JUCE_CALLTYPE Process::isForegroundProcess()
{ {

View file

@ -0,0 +1,109 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
struct ThreadPriorities
{
struct Entry
{
Thread::Priority priority;
int native;
};
#if JUCE_ANDROID
enum AndroidThreadPriority
{
THREAD_PRIORITY_AUDIO = -16,
THREAD_PRIORITY_FOREGROUND = -2,
THREAD_PRIORITY_MORE_FAVORABLE = -1,
THREAD_PRIORITY_DEFAULT = 0,
THREAD_PRIORITY_LESS_FAVORABLE = 1,
THREAD_PRIORITY_BACKGROUND = 10,
THREAD_PRIORITY_LOWEST = 19
};
#endif
inline static constexpr Entry table[]
{
#if JUCE_ANDROID
{ Thread::Priority::highest, AndroidThreadPriority::THREAD_PRIORITY_AUDIO },
{ Thread::Priority::high, AndroidThreadPriority::THREAD_PRIORITY_FOREGROUND },
{ Thread::Priority::normal, AndroidThreadPriority::THREAD_PRIORITY_DEFAULT },
{ Thread::Priority::low, AndroidThreadPriority::THREAD_PRIORITY_BACKGROUND - 5 },
{ Thread::Priority::background, AndroidThreadPriority::THREAD_PRIORITY_BACKGROUND },
#endif
#if JUCE_LINUX || JUCE_BSD
{ Thread::Priority::highest, 0 },
{ Thread::Priority::high, 0 },
{ Thread::Priority::normal, 0 },
{ Thread::Priority::low, 0 },
{ Thread::Priority::background, 0 },
#endif
#if JUCE_MAC | JUCE_IOS
{ Thread::Priority::highest, 4 },
{ Thread::Priority::high, 3 },
{ Thread::Priority::normal, 2 },
{ Thread::Priority::low, 1 },
{ Thread::Priority::background, 0 },
#endif
#if JUCE_WINDOWS
{ Thread::Priority::highest, THREAD_PRIORITY_TIME_CRITICAL },
{ Thread::Priority::high, THREAD_PRIORITY_HIGHEST },
{ Thread::Priority::normal, THREAD_PRIORITY_NORMAL },
{ Thread::Priority::low, THREAD_PRIORITY_LOWEST },
{ Thread::Priority::background, THREAD_PRIORITY_IDLE },
#endif
};
static_assert (std::size (table) == 5,
"The platform may be unsupported or there may be a priority entry missing.");
static Thread::Priority getJucePriority (const int value)
{
const auto iter = std::min_element (std::begin (table),
std::end (table),
[value] (const auto& a, const auto& b)
{
return std::abs (a.native - value) < std::abs (b.native - value);
});
jassert (iter != std::end (table));
return iter != std::end (table) ? iter->priority : Thread::Priority{};
}
static int getNativePriority (const Thread::Priority value)
{
const auto iter = std::find_if (std::begin (table),
std::end (table),
[value] (const auto& entry) { return entry.priority == value; });
jassert (iter != std::end (table));
return iter != std::end (table) ? iter->native : 0;
}
};
} // namespace juce

View file

@ -851,98 +851,131 @@ void InterProcessLock::exit()
pimpl.reset(); pimpl.reset();
} }
//============================================================================== class PosixThreadAttribute
#if JUCE_ANDROID
extern JavaVM* androidJNIJavaVM;
#endif
static void* threadEntryProc (void* userData)
{ {
auto* myself = static_cast<Thread*> (userData); public:
explicit PosixThreadAttribute (size_t stackSize)
JUCE_AUTORELEASEPOOL
{ {
juce_threadEntryPoint (myself); if (valid)
pthread_attr_setstacksize (&attr, stackSize);
} }
#if JUCE_ANDROID ~PosixThreadAttribute()
if (androidJNIJavaVM != nullptr)
{ {
void* env = nullptr; if (valid)
androidJNIJavaVM->GetEnv(&env, JNI_VERSION_1_2); pthread_attr_destroy (&attr);
// only detach if we have actually been attached
if (env != nullptr)
androidJNIJavaVM->DetachCurrentThread();
}
#endif
return nullptr;
} }
#if JUCE_ANDROID && JUCE_MODULE_AVAILABLE_juce_audio_devices && (JUCE_USE_ANDROID_OPENSLES || JUCE_USE_ANDROID_OBOE) auto* get() { return valid ? &attr : nullptr; }
#define JUCE_ANDROID_REALTIME_THREAD_AVAILABLE 1
#endif
#if JUCE_ANDROID_REALTIME_THREAD_AVAILABLE private:
extern pthread_t juce_createRealtimeAudioThread (void* (*entry) (void*), void* userPtr);
#endif
void Thread::launchThread()
{
#if JUCE_ANDROID
if (isAndroidRealtimeThread)
{
#if JUCE_ANDROID_REALTIME_THREAD_AVAILABLE
threadHandle = (void*) juce_createRealtimeAudioThread (threadEntryProc, this);
threadId = (ThreadID) threadHandle.get();
return;
#else
jassertfalse;
#endif
}
#endif
threadHandle = {};
pthread_t handle = {};
pthread_attr_t attr; pthread_attr_t attr;
pthread_attr_t* attrPtr = nullptr; bool valid { pthread_attr_init (&attr) == 0 };
};
if (pthread_attr_init (&attr) == 0) class PosixSchedulerPriority
{ {
attrPtr = &attr; public:
pthread_attr_setstacksize (attrPtr, threadStackSize); static PosixSchedulerPriority findCurrentSchedulerAndPriority()
{
int scheduler{};
sched_param param{};
pthread_getschedparam (pthread_self(), &scheduler, &param);
return { scheduler, param.sched_priority };
} }
static PosixSchedulerPriority getNativeSchedulerAndPriority (const Optional<Thread::RealtimeOptions>& rt,
if (pthread_create (&handle, attrPtr, threadEntryProc, this) == 0) [[maybe_unused]] Thread::Priority prio)
{ {
const auto isRealtime = rt.hasValue();
const auto priority = [&]
{
if (isRealtime)
{
const auto min = jmax (0, sched_get_priority_min (SCHED_RR));
const auto max = jmax (1, sched_get_priority_max (SCHED_RR));
return jmap (rt->priority, 0, 10, min, max);
}
// We only use this helper if we're on an old macos/ios platform that might
// still respect legacy pthread priorities for SCHED_OTHER.
#if JUCE_MAC || JUCE_IOS
const auto min = jmax (0, sched_get_priority_min (SCHED_OTHER));
const auto max = jmax (0, sched_get_priority_max (SCHED_OTHER));
const auto p = [prio]
{
switch (prio)
{
case Thread::Priority::highest: return 4;
case Thread::Priority::high: return 3;
case Thread::Priority::normal: return 2;
case Thread::Priority::low: return 1;
case Thread::Priority::background: return 0;
}
return 3;
}();
if (min != 0 && max != 0)
return jmap (p, 0, 4, min, max);
#endif
return 0;
}();
#if JUCE_MAC || JUCE_IOS
const auto scheduler = SCHED_OTHER;
#elif JUCE_LINUX || JUCE_BSD
const auto backgroundSched = prio == Thread::Priority::background ? SCHED_IDLE
: SCHED_OTHER;
const auto scheduler = isRealtime ? SCHED_RR : backgroundSched;
#else
const auto scheduler = 0;
#endif
return { scheduler, priority };
}
void apply ([[maybe_unused]] PosixThreadAttribute& attr) const
{
#if JUCE_LINUX || JUCE_BSD
const struct sched_param param { getPriority() };
pthread_attr_setinheritsched (attr.get(), PTHREAD_EXPLICIT_SCHED);
pthread_attr_setschedpolicy (attr.get(), getScheduler());
pthread_attr_setschedparam (attr.get(), &param);
#endif
}
constexpr int getScheduler() const { return scheduler; }
constexpr int getPriority() const { return priority; }
private:
constexpr PosixSchedulerPriority (int schedulerIn, int priorityIn)
: scheduler (schedulerIn), priority (priorityIn) {}
int scheduler;
int priority;
};
static void* makeThreadHandle (PosixThreadAttribute& attr, Thread* userData, void* (*threadEntryProc) (void*))
{
pthread_t handle = {};
if (pthread_create (&handle, attr.get(), threadEntryProc, userData) != 0)
return nullptr;
pthread_detach (handle); pthread_detach (handle);
threadHandle = (void*) handle; return (void*) handle;
threadId = (ThreadID) threadHandle.get();
}
if (attrPtr != nullptr)
pthread_attr_destroy (attrPtr);
} }
void Thread::closeThreadHandle() void Thread::closeThreadHandle()
{ {
threadId = {}; threadId = {};
threadHandle = {}; threadHandle = nullptr;
}
void Thread::killThread()
{
if (threadHandle.get() != nullptr)
{
#if JUCE_ANDROID
jassertfalse; // pthread_cancel not available!
#else
pthread_cancel ((pthread_t) threadHandle.get());
#endif
}
} }
void JUCE_CALLTYPE Thread::setCurrentThreadName (const String& name) void JUCE_CALLTYPE Thread::setCurrentThreadName (const String& name)
@ -963,41 +996,6 @@ void JUCE_CALLTYPE Thread::setCurrentThreadName (const String& name)
#endif #endif
} }
bool Thread::setThreadPriority (void* handle, int priority)
{
constexpr auto maxInputPriority = 10;
#if JUCE_LINUX || JUCE_BSD
constexpr auto lowestRrPriority = 8;
#else
constexpr auto lowestRrPriority = 0;
#endif
struct sched_param param;
int policy;
if (handle == nullptr)
handle = (void*) pthread_self();
if (pthread_getschedparam ((pthread_t) handle, &policy, &param) != 0)
return false;
policy = priority < lowestRrPriority ? SCHED_OTHER : SCHED_RR;
const auto minPriority = sched_get_priority_min (policy);
const auto maxPriority = sched_get_priority_max (policy);
param.sched_priority = [&]
{
if (policy == SCHED_OTHER)
return 0;
return jmap (priority, lowestRrPriority, maxInputPriority, minPriority, maxPriority);
}();
return pthread_setschedparam ((pthread_t) handle, policy, &param) == 0;
}
Thread::ThreadID JUCE_CALLTYPE Thread::getCurrentThreadId() Thread::ThreadID JUCE_CALLTYPE Thread::getCurrentThreadId()
{ {
return (ThreadID) pthread_self(); return (ThreadID) pthread_self();
@ -1262,145 +1260,4 @@ bool ChildProcess::start (const StringArray& args, int streamFlags)
#endif #endif
//==============================================================================
struct HighResolutionTimer::Pimpl
{
explicit Pimpl (HighResolutionTimer& t)
: owner (t)
{}
~Pimpl()
{
jassert (periodMs == 0);
stop();
}
void start (int newPeriod)
{
if (periodMs == newPeriod)
return;
if (thread.get_id() == std::this_thread::get_id())
{
periodMs = newPeriod;
return;
}
stop();
periodMs = newPeriod;
thread = std::thread ([this, newPeriod]
{
setThisThreadToRealtime ((uint64) newPeriod);
auto lastPeriod = periodMs.load();
Clock clock (lastPeriod);
std::unique_lock<std::mutex> unique_lock (timerMutex);
while (periodMs != 0)
{
clock.next();
while (periodMs != 0 && clock.wait (stopCond, unique_lock));
if (periodMs == 0)
break;
owner.hiResTimerCallback();
auto nextPeriod = periodMs.load();
if (lastPeriod != nextPeriod)
{
lastPeriod = nextPeriod;
clock = Clock (lastPeriod);
}
}
periodMs = 0;
});
}
void stop()
{
periodMs = 0;
const auto thread_id = thread.get_id();
if (thread_id == std::thread::id() || thread_id == std::this_thread::get_id())
return;
{
std::unique_lock<std::mutex> unique_lock (timerMutex);
stopCond.notify_one();
}
thread.join();
}
HighResolutionTimer& owner;
std::atomic<int> periodMs { 0 };
private:
std::thread thread;
std::condition_variable stopCond;
std::mutex timerMutex;
class Clock
{
public:
explicit Clock (std::chrono::steady_clock::rep millis) noexcept
: time (std::chrono::steady_clock::now()),
delta (std::chrono::milliseconds (millis))
{}
bool wait (std::condition_variable& cond, std::unique_lock<std::mutex>& lock) noexcept
{
return cond.wait_until (lock, time) != std::cv_status::timeout;
}
void next() noexcept
{
time += delta;
}
private:
std::chrono::time_point<std::chrono::steady_clock> time;
std::chrono::steady_clock::duration delta;
};
static bool setThisThreadToRealtime (uint64 periodMs)
{
const auto thread = pthread_self();
#if JUCE_MAC || JUCE_IOS
mach_timebase_info_data_t timebase;
mach_timebase_info (&timebase);
const auto ticksPerMs = ((double) timebase.denom * 1000000.0) / (double) timebase.numer;
const auto periodTicks = (uint32_t) jmin ((double) std::numeric_limits<uint32_t>::max(), periodMs * ticksPerMs);
thread_time_constraint_policy_data_t policy;
policy.period = periodTicks;
policy.computation = jmin ((uint32_t) 50000, policy.period);
policy.constraint = policy.period;
policy.preemptible = true;
return thread_policy_set (pthread_mach_thread_np (thread),
THREAD_TIME_CONSTRAINT_POLICY,
(thread_policy_t) &policy,
THREAD_TIME_CONSTRAINT_POLICY_COUNT) == KERN_SUCCESS;
#else
ignoreUnused (periodMs);
struct sched_param param;
param.sched_priority = sched_get_priority_max (SCHED_RR);
return pthread_setschedparam (thread, SCHED_RR, &param) == 0;
#endif
}
JUCE_DECLARE_NON_COPYABLE (Pimpl)
};
} // namespace juce } // namespace juce

View file

@ -51,7 +51,6 @@ void CriticalSection::enter() const noexcept { EnterCriticalSection ((CRI
bool CriticalSection::tryEnter() const noexcept { return TryEnterCriticalSection ((CRITICAL_SECTION*) &lock) != FALSE; } bool CriticalSection::tryEnter() const noexcept { return TryEnterCriticalSection ((CRITICAL_SECTION*) &lock) != FALSE; }
void CriticalSection::exit() const noexcept { LeaveCriticalSection ((CRITICAL_SECTION*) &lock); } void CriticalSection::exit() const noexcept { LeaveCriticalSection ((CRITICAL_SECTION*) &lock); }
//============================================================================== //==============================================================================
static unsigned int STDMETHODCALLTYPE threadEntryProc (void* userData) static unsigned int STDMETHODCALLTYPE threadEntryProc (void* userData)
{ {
@ -65,31 +64,72 @@ static unsigned int STDMETHODCALLTYPE threadEntryProc (void* userData)
return 0; return 0;
} }
void Thread::launchThread() static bool setPriorityInternal (bool isRealtime, HANDLE handle, Thread::Priority priority)
{
auto nativeThreadFlag = isRealtime ? THREAD_PRIORITY_TIME_CRITICAL
: ThreadPriorities::getNativePriority (priority);
if (isRealtime) // This should probably be a fail state too?
Process::setPriority (Process::ProcessPriority::RealtimePriority);
return SetThreadPriority (handle, nativeThreadFlag);
}
bool Thread::createNativeThread (Priority priority)
{ {
unsigned int newThreadId; unsigned int newThreadId;
threadHandle = (void*) _beginthreadex (nullptr, (unsigned int) threadStackSize, threadHandle = (void*) _beginthreadex (nullptr, (unsigned int) threadStackSize,
&threadEntryProc, this, 0, &newThreadId); &threadEntryProc, this, CREATE_SUSPENDED,
&newThreadId);
if (threadHandle != nullptr)
{
threadId = (ThreadID) (pointer_sized_int) newThreadId; threadId = (ThreadID) (pointer_sized_int) newThreadId;
if (setPriorityInternal (isRealtime(), threadHandle, priority))
{
ResumeThread (threadHandle);
return true;
}
killThread();
closeThreadHandle();
}
return false;
}
Thread::Priority Thread::getPriority() const
{
jassert (Thread::getCurrentThreadId() == getThreadId());
const auto native = GetThreadPriority (threadHandle);
return ThreadPriorities::getJucePriority (native);
}
bool Thread::setPriority (Priority priority)
{
jassert (Thread::getCurrentThreadId() == getThreadId());
return setPriorityInternal (isRealtime(), this, priority);
} }
void Thread::closeThreadHandle() void Thread::closeThreadHandle()
{ {
CloseHandle ((HANDLE) threadHandle.get()); CloseHandle (threadHandle);
threadId = nullptr; threadId = nullptr;
threadHandle = nullptr; threadHandle = nullptr;
} }
void Thread::killThread() void Thread::killThread()
{ {
if (threadHandle.get() != nullptr) if (threadHandle != nullptr)
{ {
#if JUCE_DEBUG #if JUCE_DEBUG
OutputDebugStringA ("** Warning - Forced thread termination **\n"); OutputDebugStringA ("** Warning - Forced thread termination **\n");
#endif #endif
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6258) JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6258)
TerminateThread (threadHandle.get(), 0); TerminateThread (threadHandle, 0);
JUCE_END_IGNORE_WARNINGS_MSVC JUCE_END_IGNORE_WARNINGS_MSVC
} }
} }
@ -129,23 +169,6 @@ Thread::ThreadID JUCE_CALLTYPE Thread::getCurrentThreadId()
return (ThreadID) (pointer_sized_int) GetCurrentThreadId(); return (ThreadID) (pointer_sized_int) GetCurrentThreadId();
} }
bool Thread::setThreadPriority (void* handle, int priority)
{
int pri = THREAD_PRIORITY_TIME_CRITICAL;
if (priority < 1) pri = THREAD_PRIORITY_IDLE;
else if (priority < 2) pri = THREAD_PRIORITY_LOWEST;
else if (priority < 5) pri = THREAD_PRIORITY_BELOW_NORMAL;
else if (priority < 7) pri = THREAD_PRIORITY_NORMAL;
else if (priority < 9) pri = THREAD_PRIORITY_ABOVE_NORMAL;
else if (priority < 10) pri = THREAD_PRIORITY_HIGHEST;
if (handle == nullptr)
handle = GetCurrentThread();
return SetThreadPriority (handle, pri) != FALSE;
}
void JUCE_CALLTYPE Thread::setCurrentThreadAffinityMask (const uint32 affinityMask) void JUCE_CALLTYPE Thread::setCurrentThreadAffinityMask (const uint32 affinityMask)
{ {
SetThreadAffinityMask (GetCurrentThread(), affinityMask); SetThreadAffinityMask (GetCurrentThread(), affinityMask);
@ -217,11 +240,11 @@ void juce_repeatLastProcessPriority()
} }
} }
void JUCE_CALLTYPE Process::setPriority (ProcessPriority prior) void JUCE_CALLTYPE Process::setPriority (ProcessPriority newPriority)
{ {
if (lastProcessPriority != (int) prior) if (lastProcessPriority != (int) newPriority)
{ {
lastProcessPriority = (int) prior; lastProcessPriority = (int) newPriority;
juce_repeatLastProcessPriority(); juce_repeatLastProcessPriority();
} }
} }
@ -527,56 +550,4 @@ bool ChildProcess::start (const StringArray& args, int streamFlags)
return start (escaped.trim(), streamFlags); return start (escaped.trim(), streamFlags);
} }
//==============================================================================
struct HighResolutionTimer::Pimpl
{
Pimpl (HighResolutionTimer& t) noexcept : owner (t)
{
}
~Pimpl()
{
jassert (periodMs == 0);
}
void start (int newPeriod)
{
if (newPeriod != periodMs)
{
stop();
periodMs = newPeriod;
TIMECAPS tc;
if (timeGetDevCaps (&tc, sizeof (tc)) == TIMERR_NOERROR)
{
const int actualPeriod = jlimit ((int) tc.wPeriodMin, (int) tc.wPeriodMax, newPeriod);
timerID = timeSetEvent ((UINT) actualPeriod, tc.wPeriodMin, callbackFunction, (DWORD_PTR) this,
TIME_PERIODIC | TIME_CALLBACK_FUNCTION | 0x100 /*TIME_KILL_SYNCHRONOUS*/);
}
}
}
void stop()
{
periodMs = 0;
timeKillEvent (timerID);
}
HighResolutionTimer& owner;
int periodMs = 0;
private:
unsigned int timerID;
static void STDMETHODCALLTYPE callbackFunction (UINT, UINT, DWORD_PTR userInfo, DWORD_PTR, DWORD_PTR)
{
if (Pimpl* const timer = reinterpret_cast<Pimpl*> (userInfo))
if (timer->periodMs != 0)
timer->owner.hiResTimerCallback();
}
JUCE_DECLARE_NON_COPYABLE (Pimpl)
};
} // namespace juce } // namespace juce

View file

@ -243,7 +243,6 @@ public:
*/ */
static bool isRunningInAppExtensionSandbox() noexcept; static bool isRunningInAppExtensionSandbox() noexcept;
//============================================================================== //==============================================================================
#ifndef DOXYGEN #ifndef DOXYGEN
[[deprecated ("This method was spelt wrong! Please change your code to use getCpuSpeedInMegahertz instead.")]] [[deprecated ("This method was spelt wrong! Please change your code to use getCpuSpeedInMegahertz instead.")]]

View file

@ -23,13 +23,96 @@
namespace juce namespace juce
{ {
HighResolutionTimer::HighResolutionTimer() : pimpl (new Pimpl (*this)) {} class HighResolutionTimer::Pimpl : private Thread
HighResolutionTimer::~HighResolutionTimer() { stopTimer(); } {
using steady_clock = std::chrono::steady_clock;
using milliseconds = std::chrono::milliseconds;
void HighResolutionTimer::startTimer (int periodMs) { pimpl->start (jmax (1, periodMs)); } public:
void HighResolutionTimer::stopTimer() { pimpl->stop(); } explicit Pimpl (HighResolutionTimer& ownerRef)
: Thread ("HighResolutionTimerThread"),
owner (ownerRef)
{
}
bool HighResolutionTimer::isTimerRunning() const noexcept { return pimpl->periodMs != 0; } using Thread::isThreadRunning;
int HighResolutionTimer::getTimerInterval() const noexcept { return pimpl->periodMs; }
void start (int periodMs)
{
{
const std::scoped_lock lk { mutex };
periodMillis = periodMs;
nextTickTime = steady_clock::now() + milliseconds (periodMillis);
}
if (! isThreadRunning())
startThread (Thread::Priority::high);
}
void stop()
{
{
const std::scoped_lock lk { mutex };
periodMillis = 0;
}
waitEvent.notify_one();
if (Thread::getCurrentThreadId() != getThreadId())
stopThread (-1);
}
int getPeriod() const
{
return periodMillis;
}
private:
void run() override
{
for (;;)
{
{
std::unique_lock lk { mutex };
if (waitEvent.wait_until (lk, nextTickTime, [this] { return periodMillis == 0; }))
break;
nextTickTime = steady_clock::now() + milliseconds (periodMillis);
}
owner.hiResTimerCallback();
}
}
HighResolutionTimer& owner;
std::atomic<int> periodMillis { 0 };
steady_clock::time_point nextTickTime;
std::mutex mutex;
std::condition_variable waitEvent;
};
HighResolutionTimer::HighResolutionTimer()
: pimpl (new Pimpl (*this))
{
}
HighResolutionTimer::~HighResolutionTimer()
{
stopTimer();
}
void HighResolutionTimer::startTimer (int periodMs)
{
pimpl->start (jmax (1, periodMs));
}
void HighResolutionTimer::stopTimer()
{
pimpl->stop();
}
bool HighResolutionTimer::isTimerRunning() const noexcept { return getTimerInterval() != 0; }
int HighResolutionTimer::getTimerInterval() const noexcept { return pimpl->getPeriod(); }
} // namespace juce } // namespace juce

View file

@ -93,7 +93,7 @@ public:
int getTimerInterval() const noexcept; int getTimerInterval() const noexcept;
private: private:
struct Pimpl; class Pimpl;
std::unique_ptr<Pimpl> pimpl; std::unique_ptr<Pimpl> pimpl;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HighResolutionTimer) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HighResolutionTimer)

View file

@ -50,7 +50,7 @@ public:
@param priority the process priority, where @param priority the process priority, where
0=low, 1=normal, 2=high, 3=realtime 0=low, 1=normal, 2=high, 3=realtime
*/ */
static void JUCE_CALLTYPE setPriority (const ProcessPriority priority); static void JUCE_CALLTYPE setPriority (ProcessPriority priority);
/** Kills the current process immediately. /** Kills the current process immediately.

View file

@ -22,9 +22,9 @@
namespace juce namespace juce
{ {
//==============================================================================
Thread::Thread (const String& name, size_t stackSize) Thread::Thread (const String& name, size_t stackSize) : threadName (name),
: threadName (name), threadStackSize (stackSize) threadStackSize (stackSize)
{ {
} }
@ -81,12 +81,18 @@ void Thread::threadEntryPoint()
const CurrentThreadHolder::Ptr currentThreadHolder (getCurrentThreadHolder()); const CurrentThreadHolder::Ptr currentThreadHolder (getCurrentThreadHolder());
currentThreadHolder->value = this; currentThreadHolder->value = this;
#if JUCE_ANDROID
setPriority (priority);
#endif
if (threadName.isNotEmpty()) if (threadName.isNotEmpty())
setCurrentThreadName (threadName); setCurrentThreadName (threadName);
// This 'startSuspensionEvent' protects 'threadId' which is initialised after the platform's native 'CreateThread' method.
// This ensures it has been initialised correctly before it reaches this point.
if (startSuspensionEvent.wait (10000)) if (startSuspensionEvent.wait (10000))
{ {
jassert (getCurrentThreadId() == threadId.get()); jassert (getCurrentThreadId() == threadId);
if (affinityMask != 0) if (affinityMask != 0)
setCurrentThreadAffinityMask (affinityMask); setCurrentThreadAffinityMask (affinityMask);
@ -119,42 +125,65 @@ void JUCE_API juce_threadEntryPoint (void* userData)
} }
//============================================================================== //==============================================================================
void Thread::startThread() bool Thread::startThreadInternal (Priority threadPriority)
{ {
const ScopedLock sl (startStopLock); shouldExit = false;
shouldExit = 0; // 'priority' is essentially useless on Linux as only realtime
// has any options but we need to set this here to satsify
if (threadHandle.get() == nullptr) // later queries, otherwise we get inconsistent results across
{ // platforms.
launchThread(); #if JUCE_LINUX || JUCE_BSD
setThreadPriority (threadHandle.get(), threadPriority); priority = threadPriority;
startSuspensionEvent.signal();
}
}
void Thread::startThread (int priority)
{
const ScopedLock sl (startStopLock);
if (threadHandle.get() == nullptr)
{
#if JUCE_ANDROID
isAndroidRealtimeThread = (priority == realtimeAudioPriority);
#endif #endif
threadPriority = getAdjustedPriority (priority); if (createNativeThread (threadPriority))
startThread();
}
else
{ {
setPriority (priority); startSuspensionEvent.signal();
return true;
} }
return false;
}
bool Thread::startThread()
{
return startThread (Priority::normal);
}
bool Thread::startThread (Priority threadPriority)
{
const ScopedLock sl (startStopLock);
if (threadHandle == nullptr)
{
realtimeOptions.reset();
return startThreadInternal (threadPriority);
}
return false;
}
bool Thread::startRealtimeThread (const RealtimeOptions& options)
{
const ScopedLock sl (startStopLock);
if (threadHandle == nullptr)
{
realtimeOptions = makeOptional (options);
if (startThreadInternal (Priority::normal))
return true;
realtimeOptions.reset();
}
return false;
} }
bool Thread::isThreadRunning() const bool Thread::isThreadRunning() const
{ {
return threadHandle.get() != nullptr; return threadHandle != nullptr;
} }
Thread* JUCE_CALLTYPE Thread::getCurrentThread() Thread* JUCE_CALLTYPE Thread::getCurrentThread()
@ -164,19 +193,19 @@ Thread* JUCE_CALLTYPE Thread::getCurrentThread()
Thread::ThreadID Thread::getThreadId() const noexcept Thread::ThreadID Thread::getThreadId() const noexcept
{ {
return threadId.get(); return threadId;
} }
//============================================================================== //==============================================================================
void Thread::signalThreadShouldExit() void Thread::signalThreadShouldExit()
{ {
shouldExit = 1; shouldExit = true;
listeners.call ([] (Listener& l) { l.exitSignalSent(); }); listeners.call ([] (Listener& l) { l.exitSignalSent(); });
} }
bool Thread::threadShouldExit() const bool Thread::threadShouldExit() const
{ {
return shouldExit.get() != 0; return shouldExit;
} }
bool Thread::currentThreadShouldExit() bool Thread::currentThreadShouldExit()
@ -249,40 +278,9 @@ void Thread::removeListener (Listener* listener)
listeners.remove (listener); listeners.remove (listener);
} }
//============================================================================== bool Thread::isRealtime() const
bool Thread::setPriority (int newPriority)
{ {
newPriority = getAdjustedPriority (newPriority); return realtimeOptions.hasValue();
// NB: deadlock possible if you try to set the thread prio from the thread itself,
// so using setCurrentThreadPriority instead in that case.
if (getCurrentThreadId() == getThreadId())
return setCurrentThreadPriority (newPriority);
const ScopedLock sl (startStopLock);
#if JUCE_ANDROID
bool isRealtime = (newPriority == realtimeAudioPriority);
// you cannot switch from or to an Android realtime thread once the
// thread is already running!
jassert (isThreadRunning() && (isRealtime == isAndroidRealtimeThread));
isAndroidRealtimeThread = isRealtime;
#endif
if ((! isThreadRunning()) || setThreadPriority (threadHandle.get(), newPriority))
{
threadPriority = newPriority;
return true;
}
return false;
}
bool Thread::setCurrentThreadPriority (const int newPriority)
{
return setThreadPriority ({}, getAdjustedPriority (newPriority));
} }
void Thread::setAffinityMask (const uint32 newAffinityMask) void Thread::setAffinityMask (const uint32 newAffinityMask)
@ -290,11 +288,6 @@ void Thread::setAffinityMask (const uint32 newAffinityMask)
affinityMask = newAffinityMask; affinityMask = newAffinityMask;
} }
int Thread::getAdjustedPriority (int newPriority)
{
return jlimit (0, 10, newPriority == realtimeAudioPriority ? 9 : newPriority);
}
//============================================================================== //==============================================================================
bool Thread::wait (const int timeOutMilliseconds) const bool Thread::wait (const int timeOutMilliseconds) const
{ {
@ -349,7 +342,6 @@ bool JUCE_CALLTYPE Process::isRunningUnderDebugger() noexcept
return juce_isRunningUnderDebugger(); return juce_isRunningUnderDebugger();
} }
//============================================================================== //==============================================================================
//============================================================================== //==============================================================================
#if JUCE_UNIT_TESTS #if JUCE_UNIT_TESTS

View file

@ -28,8 +28,8 @@ namespace juce
Encapsulates a thread. Encapsulates a thread.
Subclasses derive from Thread and implement the run() method, in which they Subclasses derive from Thread and implement the run() method, in which they
do their business. The thread can then be started with the startThread() method do their business. The thread can then be started with the startThread() or
and controlled with various other methods. startRealtimeThread() methods and controlled with various other methods.
This class also contains some thread-related static methods, such This class also contains some thread-related static methods, such
as sleep(), yield(), getCurrentThreadId() etc. as sleep(), yield(), getCurrentThreadId() etc.
@ -42,6 +42,46 @@ namespace juce
class JUCE_API Thread class JUCE_API Thread
{ {
public: public:
//==============================================================================
/** The different runtime priorities of non-realtime threads.
@see startThread
*/
enum class Priority
{
/** The highest possible priority that isn't a dedicated realtime thread. */
highest,
/** Makes use of performance cores and higher clocks. */
high,
/** The OS default. It will balance out across all cores. */
normal,
/** Uses efficiency cores when possible. */
low,
/** Restricted to efficiency cores on platforms that have them. */
background
};
//==============================================================================
/** A selection of options available when creating realtime threads.
@see startRealtimeThread
*/
struct RealtimeOptions
{
/** Linux only: A value with a range of 0-10, where 10 is the highest priority. */
int priority = 5;
/* iOS/macOS only: A millisecond value representing the estimated time between each
'Thread::run' call. Your thread may be penalised if you frequently
overrun this.
*/
uint32_t workDurationMs = 0;
};
//============================================================================== //==============================================================================
/** /**
Creates a thread. Creates a thread.
@ -78,23 +118,51 @@ public:
virtual void run() = 0; virtual void run() = 0;
//============================================================================== //==============================================================================
/** Starts the thread running. /** Attempts to start a new thread with default ('Priority::normal') priority.
This will cause the thread's run() method to be called by a new thread. This will cause the thread's run() method to be called by a new thread.
If this thread is already running, startThread() won't do anything. If this thread is already running, startThread() won't do anything.
If a thread cannot be created with the requested priority, this will return false
and Thread::run() will not be called. An exception to this is the Android platform,
which always starts a thread and attempts to upgrade the thread after creation.
@returns true if the thread started successfully. false if it was unsuccesful.
@see stopThread @see stopThread
*/ */
void startThread(); bool startThread();
/** Starts the thread with a given priority. /** Attempts to start a new thread with a given priority.
Launches the thread with a given priority, where 0 = lowest, 10 = highest. This will cause the thread's run() method to be called by a new thread.
If the thread is already running, its priority will be changed. If this thread is already running, startThread() won't do anything.
@see startThread, setPriority, realtimeAudioPriority If a thread cannot be created with the requested priority, this will return false
and Thread::run() will not be called. An exception to this is the Android platform,
which always starts a thread and attempts to upgrade the thread after creation.
@param newPriority Priority the thread should be assigned. This parameter is ignored
on Linux.
@returns true if the thread started successfully, false if it was unsuccesful.
@see startThread, setPriority, startRealtimeThread
*/ */
void startThread (int priority); bool startThread (Priority newPriority);
/** Starts the thread with realtime performance characteristics on platforms
that support it.
You cannot change the options of a running realtime thread, nor switch
a non-realtime thread to a realtime thread. To make these changes you must
first stop the thread and then restart with different options.
@param options Realtime options the thread should be created with.
@see startThread, RealtimeOptions
*/
bool startRealtimeThread (const RealtimeOptions& options);
/** Attempts to stop the thread running. /** Attempts to stop the thread running.
@ -198,50 +266,8 @@ public:
/** Removes a listener added with addListener. */ /** Removes a listener added with addListener. */
void removeListener (Listener*); void removeListener (Listener*);
//============================================================================== /** Returns true if this Thread represents a realtime thread. */
/** Special realtime audio thread priority bool isRealtime() const;
This priority will create a high-priority thread which is best suited
for realtime audio processing.
Currently, this priority is identical to priority 9, except when building
for Android with OpenSL/Oboe support.
In this case, JUCE will ask OpenSL/Oboe to construct a super high priority thread
specifically for realtime audio processing.
Note that this priority can only be set **before** the thread has
started. Switching to this priority, or from this priority to a different
priority, is not supported under Android and will assert.
For best performance this thread should yield at regular intervals
and not call any blocking APIs.
@see startThread, setPriority, sleep, WaitableEvent
*/
enum
{
realtimeAudioPriority = -1
};
/** Changes the thread's priority.
May return false if for some reason the priority can't be changed.
@param priority the new priority, in the range 0 (lowest) to 10 (highest). A priority
of 5 is normal.
@see realtimeAudioPriority
*/
bool setPriority (int priority);
/** Changes the priority of the caller thread.
Similar to setPriority(), but this static method acts on the caller thread.
May return false if for some reason the priority can't be changed.
@see setPriority
*/
static bool setCurrentThreadPriority (int priority);
//============================================================================== //==============================================================================
/** Sets the affinity mask for the thread. /** Sets the affinity mask for the thread.
@ -380,34 +406,58 @@ public:
static void initialiseJUCE (void* jniEnv, void* jContext); static void initialiseJUCE (void* jniEnv, void* jContext);
#endif #endif
protected:
//==============================================================================
/** Returns the current priority of this thread.
This can only be called from the target thread. Doing so from another thread
will cause an assert.
@see setPriority
*/
Priority getPriority() const;
/** Attempts to set the priority for this thread. Returns true if the new priority
was set successfully, false if not.
This can only be called from the target thread. Doing so from another thread
will cause an assert.
@param newPriority The new priority to be applied to the thread. Note: This
has no effect on Linux platforms, subsequent calls to
'getPriority' will return this value.
@see Priority
*/
bool setPriority (Priority newPriority);
private: private:
//============================================================================== //==============================================================================
const String threadName; const String threadName;
Atomic<void*> threadHandle { nullptr }; std::atomic<void*> threadHandle { nullptr };
Atomic<ThreadID> threadId = {}; std::atomic<ThreadID> threadId { nullptr };
Optional<RealtimeOptions> realtimeOptions = {};
CriticalSection startStopLock; CriticalSection startStopLock;
WaitableEvent startSuspensionEvent, defaultEvent; WaitableEvent startSuspensionEvent, defaultEvent;
int threadPriority = 5;
size_t threadStackSize; size_t threadStackSize;
uint32 affinityMask = 0; uint32 affinityMask = 0;
bool deleteOnThreadEnd = false; bool deleteOnThreadEnd = false;
Atomic<int32> shouldExit { 0 }; std::atomic<bool> shouldExit { false };
ListenerList<Listener, Array<Listener*, CriticalSection>> listeners; ListenerList<Listener, Array<Listener*, CriticalSection>> listeners;
#if JUCE_ANDROID #if JUCE_ANDROID || JUCE_LINUX || JUCE_BSD
bool isAndroidRealtimeThread = false; std::atomic<Priority> priority;
#endif #endif
#ifndef DOXYGEN #ifndef DOXYGEN
friend void JUCE_API juce_threadEntryPoint (void*); friend void JUCE_API juce_threadEntryPoint (void*);
#endif #endif
void launchThread(); bool startThreadInternal (Priority);
bool createNativeThread (Priority);
void closeThreadHandle(); void closeThreadHandle();
void killThread(); void killThread();
void threadEntryPoint(); void threadEntryPoint();
static bool setThreadPriority (void*, int);
static int getAdjustedPriority (int);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Thread) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Thread)
}; };

View file

@ -33,11 +33,14 @@ struct ThreadPool::ThreadPoolThread : public Thread
void run() override void run() override
{ {
while (! threadShouldExit()) while (! threadShouldExit())
{
if (! pool.runNextJob (*this)) if (! pool.runNextJob (*this))
wait (500); wait (500);
} }
}
std::atomic<ThreadPoolJob*> currentJob { nullptr }; std::atomic<ThreadPoolJob*> currentJob { nullptr };
ThreadPool& pool; ThreadPool& pool;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadPoolThread) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadPoolThread)
@ -90,16 +93,19 @@ ThreadPoolJob* ThreadPoolJob::getCurrentThreadPoolJob()
} }
//============================================================================== //==============================================================================
ThreadPool::ThreadPool (int numThreads, size_t threadStackSize) ThreadPool::ThreadPool (int numThreads, size_t threadStackSize, Thread::Priority priority)
{ {
jassert (numThreads > 0); // not much point having a pool without any threads! jassert (numThreads > 0); // not much point having a pool without any threads!
createThreads (numThreads, threadStackSize); for (int i = jmax (1, numThreads); --i >= 0;)
threads.add (new ThreadPoolThread (*this, threadStackSize));
for (auto* t : threads)
t->startThread (priority);
} }
ThreadPool::ThreadPool() ThreadPool::ThreadPool() : ThreadPool (SystemStats::getNumCpus(), 0, Thread::Priority::normal)
{ {
createThreads (SystemStats::getNumCpus());
} }
ThreadPool::~ThreadPool() ThreadPool::~ThreadPool()
@ -108,15 +114,6 @@ ThreadPool::~ThreadPool()
stopThreads(); stopThreads();
} }
void ThreadPool::createThreads (int numThreads, size_t threadStackSize)
{
for (int i = jmax (1, numThreads); --i >= 0;)
threads.add (new ThreadPoolThread (*this, threadStackSize));
for (auto* t : threads)
t->startThread();
}
void ThreadPool::stopThreads() void ThreadPool::stopThreads()
{ {
for (auto* t : threads) for (auto* t : threads)
@ -330,17 +327,6 @@ StringArray ThreadPool::getNamesOfAllJobs (bool onlyReturnActiveJobs) const
return s; return s;
} }
bool ThreadPool::setThreadPriorities (int newPriority)
{
bool ok = true;
for (auto* t : threads)
if (! t->setPriority (newPriority))
ok = false;
return ok;
}
ThreadPoolJob* ThreadPool::pickNextJobToRun() ThreadPoolJob* ThreadPool::pickNextJobToRun()
{ {
OwnedArray<ThreadPoolJob> deletionList; OwnedArray<ThreadPoolJob> deletionList;

View file

@ -164,8 +164,9 @@ public:
@param threadStackSize the size of the stack of each thread. If this value @param threadStackSize the size of the stack of each thread. If this value
is zero then the default stack size of the OS will is zero then the default stack size of the OS will
be used. be used.
@param priority the desired priority of each thread in the pool.
*/ */
ThreadPool (int numberOfThreads, size_t threadStackSize = 0); ThreadPool (int numberOfThreads, size_t threadStackSize = 0, Thread::Priority priority = Thread::Priority::normal);
/** Creates a thread pool with one thread per CPU core. /** Creates a thread pool with one thread per CPU core.
Once you've created a pool, you can give it some jobs by calling addJob(). Once you've created a pool, you can give it some jobs by calling addJob().
@ -309,13 +310,6 @@ public:
*/ */
StringArray getNamesOfAllJobs (bool onlyReturnActiveJobs) const; StringArray getNamesOfAllJobs (bool onlyReturnActiveJobs) const;
/** Changes the priority of all the threads.
This will call Thread::setPriority() for each thread in the pool.
May return false if for some reason the priority can't be changed.
*/
bool setThreadPriorities (int newPriority);
private: private:
//============================================================================== //==============================================================================
Array<ThreadPoolJob*> jobs; Array<ThreadPoolJob*> jobs;
@ -330,7 +324,6 @@ private:
bool runNextJob (ThreadPoolThread&); bool runNextJob (ThreadPoolThread&);
ThreadPoolJob* pickNextJobToRun(); ThreadPoolJob* pickNextJobToRun();
void addToDeleteList (OwnedArray<ThreadPoolJob>&, ThreadPoolJob*) const; void addToDeleteList (OwnedArray<ThreadPoolJob>&, ThreadPoolJob*) const;
void createThreads (int numThreads, size_t threadStackSize = 0);
void stopThreads(); void stopThreads();
// Note that this method has changed, and no longer has a parameter to indicate // Note that this method has changed, and no longer has a parameter to indicate

View file

@ -51,7 +51,7 @@ struct ChildProcessPingThread : public Thread,
pingReceived(); pingReceived();
} }
void startPinging() { startThread (4); } void startPinging() { startThread (Priority::low); }
void pingReceived() noexcept { countdown = timeoutMs / 1000 + 1; } void pingReceived() noexcept { countdown = timeoutMs / 1000 + 1; }
void triggerConnectionLostMessage() { triggerAsyncUpdate(); } void triggerConnectionLostMessage() { triggerAsyncUpdate(); }

View file

@ -41,7 +41,7 @@ NetworkServiceDiscovery::Advertiser::Advertiser (const String& serviceTypeUID,
message.setAttribute ("address", String()); message.setAttribute ("address", String());
message.setAttribute ("port", connectionPort); message.setAttribute ("port", connectionPort);
startThread (2); startThread (Priority::background);
} }
NetworkServiceDiscovery::Advertiser::~Advertiser() NetworkServiceDiscovery::Advertiser::~Advertiser()
@ -92,7 +92,7 @@ NetworkServiceDiscovery::AvailableServiceList::AvailableServiceList (const Strin
#endif #endif
socket.bindToPort (broadcastPort); socket.bindToPort (broadcastPort);
startThread (2); startThread (Priority::background);
} }
NetworkServiceDiscovery::AvailableServiceList::~AvailableServiceList() NetworkServiceDiscovery::AvailableServiceList::~AvailableServiceList()

View file

@ -303,7 +303,7 @@ private:
void handleAsyncUpdate() override void handleAsyncUpdate() override
{ {
startThread (7); startThread (Priority::high);
} }
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TimerThread) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TimerThread)

View file

@ -64,7 +64,7 @@ FileBrowserComponent::FileBrowserComponent (int flags_,
} }
// The thread must be started before the DirectoryContentsList attempts to scan any file // The thread must be started before the DirectoryContentsList attempts to scan any file
thread.startThread (4); thread.startThread (Thread::Priority::low);
fileList.reset (new DirectoryContentsList (this, thread)); fileList.reset (new DirectoryContentsList (this, thread));
fileList->setDirectory (currentRoot, true, true); fileList->setDirectory (currentRoot, true, true);

View file

@ -1403,7 +1403,7 @@ public:
monitor (mon) monitor (mon)
{ {
listeners.push_back (listener); listeners.push_back (listener);
startThread (10); startThread (Priority::highest);
} }
~VSyncThread() override ~VSyncThread() override

View file

@ -56,11 +56,11 @@ ThreadWithProgressWindow::~ThreadWithProgressWindow()
stopThread (timeOutMsWhenCancelling); stopThread (timeOutMsWhenCancelling);
} }
void ThreadWithProgressWindow::launchThread (int priority) void ThreadWithProgressWindow::launchThread (Priority threadPriority)
{ {
JUCE_ASSERT_MESSAGE_THREAD JUCE_ASSERT_MESSAGE_THREAD
startThread (priority); startThread (threadPriority);
startTimer (100); startTimer (100);
{ {
@ -105,9 +105,9 @@ void ThreadWithProgressWindow::timerCallback()
void ThreadWithProgressWindow::threadComplete (bool) {} void ThreadWithProgressWindow::threadComplete (bool) {}
#if JUCE_MODAL_LOOPS_PERMITTED #if JUCE_MODAL_LOOPS_PERMITTED
bool ThreadWithProgressWindow::runThread (const int priority) bool ThreadWithProgressWindow::runThread (Priority threadPriority)
{ {
launchThread (priority); launchThread (threadPriority);
while (isTimerRunning()) while (isTimerRunning())
MessageManager::getInstance()->runDispatchLoopUntil (5); MessageManager::getInstance()->runDispatchLoopUntil (5);

View file

@ -122,10 +122,10 @@ public:
Before returning, the dialog box will be hidden. Before returning, the dialog box will be hidden.
@param priority the priority to use when starting the thread - see @param priority the priority to use when starting the thread - see
Thread::startThread() for values Thread::Priority for values
@returns true if the thread finished normally; false if the user pressed cancel @returns true if the thread finished normally; false if the user pressed cancel
*/ */
bool runThread (int priority = 5); bool runThread (Priority Priority = Priority::normal);
#endif #endif
/** Starts the thread and returns. /** Starts the thread and returns.
@ -135,9 +135,9 @@ public:
hidden and the threadComplete() method will be called. hidden and the threadComplete() method will be called.
@param priority the priority to use when starting the thread - see @param priority the priority to use when starting the thread - see
Thread::startThread() for values Thread::Priority for values
*/ */
void launchThread (int priority = 5); void launchThread (Priority priority = Priority::normal);
/** The thread should call this periodically to update the position of the progress bar. /** The thread should call this periodically to update the position of the progress bar.

View file

@ -58,7 +58,7 @@ struct OnlineUnlockForm::OverlayComp : public Component,
cancelButton->addListener (this); cancelButton->addListener (this);
} }
startThread (4); startThread (Priority::normal);
} }
~OverlayComp() override ~OverlayComp() override