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
=====================
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
------
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
future.
Change
------
The function AudioIODeviceCallback::audioDeviceIOCallback() was removed.

View file

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

View file

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

View file

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

View file

@ -63,7 +63,7 @@ public:
SharedTimeSliceThread()
: 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),
Thread ("JUCE Demo Thread")
{
// give the threads a random priority, so some will move more
// smoothly than others..
startThread (Random::getSystemRandom().nextInt (3) + 3);
startThread();
}
~DemoThread() override

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -203,6 +203,7 @@
#include <sys/ptrace.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/sysinfo.h>
#include <sys/time.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
JUCE_API void JUCE_CALLTYPE Process::setPriority (ProcessPriority prior)
#if JUCE_ANDROID && JUCE_MODULE_AVAILABLE_juce_audio_devices && (JUCE_USE_ANDROID_OPENSLES || JUCE_USE_ANDROID_OBOE)
#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())
{
#if JUCE_ANDROID_REALTIME_THREAD_AVAILABLE
threadHandle = (void*) juce_createRealtimeAudioThread (threadEntryProc, this);
threadId = (ThreadID) threadHandle.get();
return threadId != nullptr;
#else
jassertfalse;
#endif
}
struct sched_param param;
int policy, maxp, minp;
PosixThreadAttribute attr { threadStackSize };
threadId = threadHandle = makeThreadHandle (attr, this, [] (void* userData) -> void*
{
auto* myself = static_cast<Thread*> (userData);
const int p = (int) prior;
juce_threadEntryPoint (myself);
if (p <= 1)
policy = SCHED_OTHER;
else
policy = SCHED_RR;
if (androidJNIJavaVM != nullptr)
{
void* env = nullptr;
androidJNIJavaVM->GetEnv (&env, JNI_VERSION_1_2);
minp = sched_get_priority_min (policy);
maxp = sched_get_priority_max (policy);
// only detach if we have actually been attached
if (env != nullptr)
androidJNIJavaVM->DetachCurrentThread();
}
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);
return nullptr;
});
pthread_setschedparam (pthread_self(), policy, &param);
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
{
StringArray lines;

View file

@ -28,27 +28,49 @@ namespace juce
live in juce_posix_SharedCode.h!
*/
//==============================================================================
JUCE_API void JUCE_CALLTYPE Process::setPriority (const ProcessPriority prior)
bool Thread::createNativeThread (Priority)
{
auto policy = (prior <= NormalPriority) ? SCHED_OTHER : SCHED_RR;
auto minp = sched_get_priority_min (policy);
auto maxp = sched_get_priority_max (policy);
PosixThreadAttribute attr { threadStackSize };
PosixSchedulerPriority::getNativeSchedulerAndPriority (realtimeOptions, {}).apply (attr);
struct sched_param param;
switch (prior)
threadId = threadHandle = makeThreadHandle (attr, this, [] (void* userData) -> void*
{
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;
}
auto* myself = static_cast<Thread*> (userData);
pthread_setschedparam (pthread_self(), policy, &param);
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 (ProcessPriority) {}
static bool swapUserAndEffectiveUser()
{
auto result1 = setreuid (geteuid(), getuid());

View file

@ -29,9 +29,139 @@ namespace juce
*/
#if JUCE_IOS
bool isIOSAppActive = true;
bool isIOSAppActive = true;
#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()
{

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();
}
//==============================================================================
#if JUCE_ANDROID
extern JavaVM* androidJNIJavaVM;
#endif
static void* threadEntryProc (void* userData)
class PosixThreadAttribute
{
auto* myself = static_cast<Thread*> (userData);
JUCE_AUTORELEASEPOOL
public:
explicit PosixThreadAttribute (size_t stackSize)
{
juce_threadEntryPoint (myself);
if (valid)
pthread_attr_setstacksize (&attr, stackSize);
}
#if JUCE_ANDROID
if (androidJNIJavaVM != nullptr)
~PosixThreadAttribute()
{
void* env = nullptr;
androidJNIJavaVM->GetEnv(&env, JNI_VERSION_1_2);
// only detach if we have actually been attached
if (env != nullptr)
androidJNIJavaVM->DetachCurrentThread();
if (valid)
pthread_attr_destroy (&attr);
}
#endif
return nullptr;
}
auto* get() { return valid ? &attr : nullptr; }
#if JUCE_ANDROID && JUCE_MODULE_AVAILABLE_juce_audio_devices && (JUCE_USE_ANDROID_OPENSLES || JUCE_USE_ANDROID_OBOE)
#define JUCE_ANDROID_REALTIME_THREAD_AVAILABLE 1
#endif
#if JUCE_ANDROID_REALTIME_THREAD_AVAILABLE
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 = {};
private:
pthread_attr_t attr;
pthread_attr_t* attrPtr = nullptr;
bool valid { pthread_attr_init (&attr) == 0 };
};
if (pthread_attr_init (&attr) == 0)
class PosixSchedulerPriority
{
public:
static PosixSchedulerPriority findCurrentSchedulerAndPriority()
{
attrPtr = &attr;
pthread_attr_setstacksize (attrPtr, threadStackSize);
int scheduler{};
sched_param param{};
pthread_getschedparam (pthread_self(), &scheduler, &param);
return { scheduler, param.sched_priority };
}
if (pthread_create (&handle, attrPtr, threadEntryProc, this) == 0)
static PosixSchedulerPriority getNativeSchedulerAndPriority (const Optional<Thread::RealtimeOptions>& rt,
[[maybe_unused]] Thread::Priority prio)
{
pthread_detach (handle);
threadHandle = (void*) handle;
threadId = (ThreadID) threadHandle.get();
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 };
}
if (attrPtr != nullptr)
pthread_attr_destroy (attrPtr);
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);
return (void*) handle;
}
void Thread::closeThreadHandle()
{
threadId = {};
threadHandle = {};
}
void Thread::killThread()
{
if (threadHandle.get() != nullptr)
{
#if JUCE_ANDROID
jassertfalse; // pthread_cancel not available!
#else
pthread_cancel ((pthread_t) threadHandle.get());
#endif
}
threadHandle = nullptr;
}
void JUCE_CALLTYPE Thread::setCurrentThreadName (const String& name)
@ -963,41 +996,6 @@ void JUCE_CALLTYPE Thread::setCurrentThreadName (const String& name)
#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()
{
return (ThreadID) pthread_self();
@ -1262,145 +1260,4 @@ bool ChildProcess::start (const StringArray& args, int streamFlags)
#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

View file

@ -51,7 +51,6 @@ void CriticalSection::enter() const noexcept { EnterCriticalSection ((CRI
bool CriticalSection::tryEnter() const noexcept { return TryEnterCriticalSection ((CRITICAL_SECTION*) &lock) != FALSE; }
void CriticalSection::exit() const noexcept { LeaveCriticalSection ((CRITICAL_SECTION*) &lock); }
//==============================================================================
static unsigned int STDMETHODCALLTYPE threadEntryProc (void* userData)
{
@ -65,31 +64,72 @@ static unsigned int STDMETHODCALLTYPE threadEntryProc (void* userData)
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;
threadHandle = (void*) _beginthreadex (nullptr, (unsigned int) threadStackSize,
&threadEntryProc, this, 0, &newThreadId);
threadId = (ThreadID) (pointer_sized_int) newThreadId;
&threadEntryProc, this, CREATE_SUSPENDED,
&newThreadId);
if (threadHandle != nullptr)
{
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()
{
CloseHandle ((HANDLE) threadHandle.get());
CloseHandle (threadHandle);
threadId = nullptr;
threadHandle = nullptr;
}
void Thread::killThread()
{
if (threadHandle.get() != nullptr)
if (threadHandle != nullptr)
{
#if JUCE_DEBUG
OutputDebugStringA ("** Warning - Forced thread termination **\n");
#endif
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6258)
TerminateThread (threadHandle.get(), 0);
TerminateThread (threadHandle, 0);
JUCE_END_IGNORE_WARNINGS_MSVC
}
}
@ -129,23 +169,6 @@ Thread::ThreadID JUCE_CALLTYPE Thread::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)
{
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();
}
}
@ -527,56 +550,4 @@ bool ChildProcess::start (const StringArray& args, int 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

View file

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

View file

@ -23,13 +23,96 @@
namespace juce
{
HighResolutionTimer::HighResolutionTimer() : pimpl (new Pimpl (*this)) {}
HighResolutionTimer::~HighResolutionTimer() { stopTimer(); }
class HighResolutionTimer::Pimpl : private Thread
{
using steady_clock = std::chrono::steady_clock;
using milliseconds = std::chrono::milliseconds;
void HighResolutionTimer::startTimer (int periodMs) { pimpl->start (jmax (1, periodMs)); }
void HighResolutionTimer::stopTimer() { pimpl->stop(); }
public:
explicit Pimpl (HighResolutionTimer& ownerRef)
: Thread ("HighResolutionTimerThread"),
owner (ownerRef)
{
}
bool HighResolutionTimer::isTimerRunning() const noexcept { return pimpl->periodMs != 0; }
int HighResolutionTimer::getTimerInterval() const noexcept { return pimpl->periodMs; }
using Thread::isThreadRunning;
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

View file

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

View file

@ -50,7 +50,7 @@ public:
@param priority the process priority, where
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.

View file

@ -22,9 +22,9 @@
namespace juce
{
Thread::Thread (const String& name, size_t stackSize)
: threadName (name), threadStackSize (stackSize)
//==============================================================================
Thread::Thread (const String& name, size_t stackSize) : threadName (name),
threadStackSize (stackSize)
{
}
@ -81,12 +81,18 @@ void Thread::threadEntryPoint()
const CurrentThreadHolder::Ptr currentThreadHolder (getCurrentThreadHolder());
currentThreadHolder->value = this;
#if JUCE_ANDROID
setPriority (priority);
#endif
if (threadName.isNotEmpty())
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))
{
jassert (getCurrentThreadId() == threadId.get());
jassert (getCurrentThreadId() == threadId);
if (affinityMask != 0)
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
// later queries, otherwise we get inconsistent results across
// platforms.
#if JUCE_LINUX || JUCE_BSD
priority = threadPriority;
#endif
if (threadHandle.get() == nullptr)
if (createNativeThread (threadPriority))
{
launchThread();
setThreadPriority (threadHandle.get(), threadPriority);
startSuspensionEvent.signal();
return true;
}
return false;
}
void Thread::startThread (int priority)
bool Thread::startThread()
{
return startThread (Priority::normal);
}
bool Thread::startThread (Priority threadPriority)
{
const ScopedLock sl (startStopLock);
if (threadHandle.get() == nullptr)
if (threadHandle == nullptr)
{
#if JUCE_ANDROID
isAndroidRealtimeThread = (priority == realtimeAudioPriority);
#endif
realtimeOptions.reset();
return startThreadInternal (threadPriority);
}
threadPriority = getAdjustedPriority (priority);
startThread();
}
else
return false;
}
bool Thread::startRealtimeThread (const RealtimeOptions& options)
{
const ScopedLock sl (startStopLock);
if (threadHandle == nullptr)
{
setPriority (priority);
realtimeOptions = makeOptional (options);
if (startThreadInternal (Priority::normal))
return true;
realtimeOptions.reset();
}
return false;
}
bool Thread::isThreadRunning() const
{
return threadHandle.get() != nullptr;
return threadHandle != nullptr;
}
Thread* JUCE_CALLTYPE Thread::getCurrentThread()
@ -164,19 +193,19 @@ Thread* JUCE_CALLTYPE Thread::getCurrentThread()
Thread::ThreadID Thread::getThreadId() const noexcept
{
return threadId.get();
return threadId;
}
//==============================================================================
void Thread::signalThreadShouldExit()
{
shouldExit = 1;
shouldExit = true;
listeners.call ([] (Listener& l) { l.exitSignalSent(); });
}
bool Thread::threadShouldExit() const
{
return shouldExit.get() != 0;
return shouldExit;
}
bool Thread::currentThreadShouldExit()
@ -249,40 +278,9 @@ void Thread::removeListener (Listener* listener)
listeners.remove (listener);
}
//==============================================================================
bool Thread::setPriority (int newPriority)
bool Thread::isRealtime() const
{
newPriority = getAdjustedPriority (newPriority);
// 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));
return realtimeOptions.hasValue();
}
void Thread::setAffinityMask (const uint32 newAffinityMask)
@ -290,11 +288,6 @@ void Thread::setAffinityMask (const uint32 newAffinityMask)
affinityMask = newAffinityMask;
}
int Thread::getAdjustedPriority (int newPriority)
{
return jlimit (0, 10, newPriority == realtimeAudioPriority ? 9 : newPriority);
}
//==============================================================================
bool Thread::wait (const int timeOutMilliseconds) const
{
@ -349,7 +342,6 @@ bool JUCE_CALLTYPE Process::isRunningUnderDebugger() noexcept
return juce_isRunningUnderDebugger();
}
//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS

View file

@ -28,8 +28,8 @@ namespace juce
Encapsulates a thread.
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
and controlled with various other methods.
do their business. The thread can then be started with the startThread() or
startRealtimeThread() methods and controlled with various other methods.
This class also contains some thread-related static methods, such
as sleep(), yield(), getCurrentThreadId() etc.
@ -42,6 +42,46 @@ namespace juce
class JUCE_API Thread
{
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.
@ -78,23 +118,51 @@ public:
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.
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
*/
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.
If the thread is already running, its priority will be changed.
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.
@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.
@ -198,50 +266,8 @@ public:
/** Removes a listener added with addListener. */
void removeListener (Listener*);
//==============================================================================
/** Special realtime audio thread priority
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);
/** Returns true if this Thread represents a realtime thread. */
bool isRealtime() const;
//==============================================================================
/** Sets the affinity mask for the thread.
@ -380,34 +406,58 @@ public:
static void initialiseJUCE (void* jniEnv, void* jContext);
#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:
//==============================================================================
const String threadName;
Atomic<void*> threadHandle { nullptr };
Atomic<ThreadID> threadId = {};
std::atomic<void*> threadHandle { nullptr };
std::atomic<ThreadID> threadId { nullptr };
Optional<RealtimeOptions> realtimeOptions = {};
CriticalSection startStopLock;
WaitableEvent startSuspensionEvent, defaultEvent;
int threadPriority = 5;
size_t threadStackSize;
uint32 affinityMask = 0;
bool deleteOnThreadEnd = false;
Atomic<int32> shouldExit { 0 };
std::atomic<bool> shouldExit { false };
ListenerList<Listener, Array<Listener*, CriticalSection>> listeners;
#if JUCE_ANDROID
bool isAndroidRealtimeThread = false;
#if JUCE_ANDROID || JUCE_LINUX || JUCE_BSD
std::atomic<Priority> priority;
#endif
#ifndef DOXYGEN
friend void JUCE_API juce_threadEntryPoint (void*);
#endif
void launchThread();
bool startThreadInternal (Priority);
bool createNativeThread (Priority);
void closeThreadHandle();
void killThread();
void threadEntryPoint();
static bool setThreadPriority (void*, int);
static int getAdjustedPriority (int);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Thread)
};

View file

@ -33,11 +33,14 @@ struct ThreadPool::ThreadPoolThread : public Thread
void run() override
{
while (! threadShouldExit())
{
if (! pool.runNextJob (*this))
wait (500);
}
}
std::atomic<ThreadPoolJob*> currentJob { nullptr };
ThreadPool& pool;
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!
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()
@ -108,15 +114,6 @@ ThreadPool::~ThreadPool()
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()
{
for (auto* t : threads)
@ -330,17 +327,6 @@ StringArray ThreadPool::getNamesOfAllJobs (bool onlyReturnActiveJobs) const
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()
{
OwnedArray<ThreadPoolJob> deletionList;

View file

@ -164,8 +164,9 @@ public:
@param threadStackSize the size of the stack of each thread. If this value
is zero then the default stack size of the OS will
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.
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;
/** 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:
//==============================================================================
Array<ThreadPoolJob*> jobs;
@ -330,7 +324,6 @@ private:
bool runNextJob (ThreadPoolThread&);
ThreadPoolJob* pickNextJobToRun();
void addToDeleteList (OwnedArray<ThreadPoolJob>&, ThreadPoolJob*) const;
void createThreads (int numThreads, size_t threadStackSize = 0);
void stopThreads();
// 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();
}
void startPinging() { startThread (4); }
void startPinging() { startThread (Priority::low); }
void pingReceived() noexcept { countdown = timeoutMs / 1000 + 1; }
void triggerConnectionLostMessage() { triggerAsyncUpdate(); }

View file

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

View file

@ -303,7 +303,7 @@ private:
void handleAsyncUpdate() override
{
startThread (7);
startThread (Priority::high);
}
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
thread.startThread (4);
thread.startThread (Thread::Priority::low);
fileList.reset (new DirectoryContentsList (this, thread));
fileList->setDirectory (currentRoot, true, true);

View file

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

View file

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

View file

@ -122,10 +122,10 @@ public:
Before returning, the dialog box will be hidden.
@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
*/
bool runThread (int priority = 5);
bool runThread (Priority Priority = Priority::normal);
#endif
/** Starts the thread and returns.
@ -135,9 +135,9 @@ public:
hidden and the threadComplete() method will be called.
@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.

View file

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