Move many Thread related classes

This commit is contained in:
Vinnie Falco
2013-10-04 13:49:24 -07:00
parent 93e9d8622e
commit 6c7f5d093c
48 changed files with 1043 additions and 2158 deletions

50
beast/threads/LockGuard.h Normal file
View File

@@ -0,0 +1,50 @@
//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
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.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef BEAST_THREADS_LOCKGUARD_H_INCLUDED
#define BEAST_THREADS_LOCKGUARD_H_INCLUDED
#include "../Uncopyable.h"
namespace beast {
template <typename Mutex>
class LockGuard : public Uncopyable
{
public:
typedef Mutex MutexType;
explicit LockGuard (Mutex const& mutex)
: m_mutex (mutex)
{
m_mutex.lock();
}
~LockGuard ()
{
m_mutex.unlock();
}
private:
Mutex const& m_mutex;
};
}
#endif

View File

@@ -0,0 +1,84 @@
//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
Portions of this file are from JUCE.
Copyright (c) 2013 - Raw Material Software Ltd.
Please visit http://www.juce.com
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.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef BEAST_THREADS_RECURSIVEMUTEX_H_INCLUDED
#define BEAST_THREADS_RECURSIVEMUTEX_H_INCLUDED
#include "../Config.h"
#include "LockGuard.h"
#include "UnlockGuard.h"
#include "TryLockGuard.h"
#if ! BEAST_WINDOWS
#include <pthread.h>
#endif
namespace beast {
class RecursiveMutex
{
public:
typedef LockGuard <RecursiveMutex> ScopedLockType;
typedef UnlockGuard <RecursiveMutex> ScopedUnlockType;
typedef TryLockGuard <RecursiveMutex> ScopedTryLockType;
/** Create the mutex.
The mutux is initially unowned.
*/
RecursiveMutex ();
/** Destroy the mutex.
If the lock is owned, the result is undefined.
*/
~RecursiveMutex ();
// Boost concept compatibility:
// http://www.boost.org/doc/libs/1_54_0/doc/html/thread/synchronization.html#thread.synchronization.mutex_concepts
/** BasicLockable */
/** @{ */
void lock () const;
void unlock () const;
/** @} */
/** Lockable */
bool try_lock () const;
private:
// To avoid including windows.h in the public Beast headers, we'll just
// reserve storage here that's big enough to be used internally as a windows
// CRITICAL_SECTION structure.
#if BEAST_WINDOWS
# if BEAST_64BIT
char section[44];
# else
char section[24];
# endif
#else
mutable pthread_mutex_t mutex;
#endif
};
}
#endif

View File

@@ -0,0 +1,618 @@
//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
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.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef BEAST_THREADS_SERVICEQUEUE_H_INCLUDED
#define BEAST_THREADS_SERVICEQUEUE_H_INCLUDED
#include "../chrono/CPUMeter.h"
#include "../intrusive/List.h"
#include "../intrusive/LockFreeStack.h"
#include "SharedData.h"
#include "ThreadLocalValue.h"
#include "WaitableEvent.h"
namespace beast {
namespace detail {
//------------------------------------------------------------------------------
// VFALCO NOTE This allocator is a work in progress
#if 0
class ServiceQueueAllocatorArena : public SharedObject
{
public:
typedef std::size_t size_type;
class Page : public LockFreeStack <Page>::Node
{
public:
size_type const m_pageBytes;
Atomic <int> m_refs;
char* m_pos;
bool m_full;
size_type m_count;
static std::size_t overhead()
{ return sizeof (Page); }
static std::size_t pointer_overhead()
{ return sizeof (Page*); }
// pageBytes doesn't include the Page structure
explicit Page (size_type pageBytes)
: m_pageBytes (pageBytes)
, m_pos (begin())
, m_full (false)
, m_count (0)
{
}
~Page ()
{
// This means someone forgot to deallocate something
bassert (!m_full && m_refs.get() == 0);
}
static Page* create (size_type pageBytes)
{
return new (new uint8[pageBytes + overhead()]) Page (pageBytes);
}
static void destroy (Page* page)
{
page->~Page();
delete[] ((uint8*)page);
}
void reset ()
{
m_refs.set (0);
m_pos = begin();
m_full = false;
m_count = 0;
}
bool full() const
{
return m_full;
}
void* allocate (size_type n)
{
size_type const needed (n + pointer_overhead());
char* pos = m_pos + needed;
if (pos > end())
{
m_full = true;
return nullptr;
}
++m_refs;
void* p (m_pos + pointer_overhead());
get_page(p) = this;
m_pos = pos;
++m_count;
return p;
}
char* begin() const
{
return const_cast <char*> (
reinterpret_cast <char const*> (this) + overhead());
}
char const* end() const
{
return begin() + m_pageBytes;
}
// Returns true if the page can be recycled
bool deallocate (void* p, size_type)
{
bool const unused ((--m_refs) == 0);
return unused && m_full;
}
// Returns a reference to the per-allocation overhead area
static Page*& get_page (void* p)
{
return *reinterpret_cast <Page**>(
static_cast <char*>(p) - pointer_overhead());
}
};
struct State
{
State()
{
}
~State()
{
// If this goes off, someone forgot to call deallocate!
bassert (full.get() == 0);
destroy (active);
destroy (recycle);
}
void destroy (LockFreeStack <Page>& stack)
{
for(;;)
{
Page* const page (stack.pop_front());
if (page == nullptr)
break;
Page::destroy (page);
}
}
Atomic <int> full;
Atomic <int> page_count;
LockFreeStack <Page> active;
LockFreeStack <Page> recycle;
};
typedef SharedData <State> SharedState;
size_type const m_maxBytes;
SharedState m_state;
explicit ServiceQueueAllocatorArena (size_type maxBytes = 16 * 1024)
: m_maxBytes (maxBytes)
{
}
~ServiceQueueAllocatorArena()
{
}
void* allocate (size_type n)
{
SharedState::UnlockedAccess state (m_state);
// Loop until we satisfy the allocation from an
// active page, or we run out of active pages.
//
for (;;)
{
// Acquire ownership of an active page
// This prevents other threads from seeing it.
Page* page (state->active.pop_front());
if (page == nullptr)
break;
void* p = page->allocate (n);
if (p != nullptr)
{
// Put the page back so other threads can use it
state->active.push_front (page);
return p;
}
// Page is full, count it for diagnostics
++state->full;
}
// No active page, get a recycled page or create a new page.
//
Page* page (state->recycle.pop_front());
if (page == nullptr)
{
page = Page::create (std::max (m_maxBytes, n));
++state->page_count;
}
void* p = page->allocate (n);
bassert (p != nullptr);
// Throw page into the active list so other threads can use it
state->active.push_front (page);
return p;
}
void deallocate (void* p, size_type n)
{
SharedState::UnlockedAccess state (m_state);
Page* const page (Page::get_page(p));
if (page->deallocate (p, n))
{
--state->full;
page->reset();
Page::destroy (page);
//state->recycle.push_front (page);
}
}
};
//------------------------------------------------------------------------------
template <typename T>
struct ServiceQueueAllocator
{
typedef T value_type;
typedef T* pointer;
typedef T& reference;
typedef T const* const_pointer;
typedef T const& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
ServiceQueueAllocator ()
: m_arena (new ServiceQueueAllocatorArena)
{
}
ServiceQueueAllocator (ServiceQueueAllocator const& other)
: m_arena (other.m_arena)
{
}
template <typename U>
ServiceQueueAllocator (ServiceQueueAllocator <U> const& other)
: m_arena (other.m_arena)
{
}
template <typename U>
struct rebind
{
typedef ServiceQueueAllocator <U> other;
};
pointer address (reference x) const
{
return &x;
}
const_pointer address (const_reference x) const
{
return &x;
}
pointer allocate (size_type n,
std::allocator<void>::const_pointer = nullptr) const
{
size_type const bytes (n * sizeof (value_type));
pointer const p (static_cast <pointer> (
m_arena->allocate (bytes)));
return p;
}
void deallocate (pointer p, size_type n) const
{
size_type const bytes = (n * sizeof (value_type));
m_arena->deallocate (p, bytes);
}
size_type max_size () const
{
return std::numeric_limits <size_type>::max () / sizeof (value_type);
}
void construct (pointer p, const_reference val) const
{
new ((void *)p) value_type (val);
}
void destroy (pointer p) const
{
p->~value_type ();
}
private:
template <typename>
friend struct ServiceQueueAllocator;
SharedPtr <ServiceQueueAllocatorArena> m_arena;
};
#endif
//------------------------------------------------------------------------------
class ServiceQueueBase
{
public:
ServiceQueueBase();
~ServiceQueueBase();
std::size_t poll();
std::size_t poll_one();
std::size_t run();
std::size_t run_one();
void stop();
bool stopped() const
{ return m_stopped.get() != 0; }
void reset();
protected:
class Item;
class Waiter;
class ScopedServiceThread;
void wait();
void enqueue (Item* item);
virtual std::size_t dequeue() = 0;
virtual Waiter* new_waiter() = 0;
//--------------------------------------------------------------------------
class Item : public List <Item>::Node
{
public:
virtual ~Item() { }
virtual void operator()() = 0;
virtual std::size_t size() const = 0;
};
//--------------------------------------------------------------------------
class Waiter : public LockFreeStack <Waiter>::Node
{
public:
Waiter()
{ }
void wait()
{ m_event.wait(); }
void signal()
{ m_event.signal(); }
private:
WaitableEvent m_event;
};
//--------------------------------------------------------------------------
struct State
{
// handlers
List <Item> handlers;
LockFreeStack <Waiter> waiting;
LockFreeStack <Waiter> unused;
};
typedef SharedData <State> SharedState;
SharedState m_state;
CPUMeter m_cpuMeter;
Atomic <int> m_stopped;
static ThreadLocalValue <ServiceQueueBase*> s_service;
};
}
//------------------------------------------------------------------------------
/** A queue for disatching function calls on other threads.
Handlers are guaranteed to be called only from threads that are currently
calling run, run_one, poll, or poll_one.
*/
template <class Allocator = std::allocator <char> >
class ServiceQueueType : public detail::ServiceQueueBase
{
private:
using ServiceQueueBase::Item;
using ServiceQueueBase::Waiter;
template <typename Handler>
class ItemType : public Item
{
public:
explicit ItemType (BEAST_MOVE_ARG(Handler) handler)
: m_handler (BEAST_MOVE_CAST(Handler)(handler))
{ }
void operator() ()
{ m_handler(); }
std::size_t size() const
{ return sizeof (*this); }
private:
Handler m_handler;
};
public:
typedef Allocator allocator_type; // for std::uses_allocator<>
explicit ServiceQueueType (int expectedConcurrency = 1,
Allocator alloc = Allocator())
: m_alloc (alloc)
{
typename Allocator::template rebind <Waiter>::other a (m_alloc);
SharedState::Access state (m_state);
while (expectedConcurrency--)
{
state->unused.push_front (new (a.allocate (1)) Waiter);
}
}
~ServiceQueueType()
{
SharedState::Access state (m_state);
// Must be empty
bassert (state->handlers.empty());
bassert (state->waiting.empty());
typename Allocator::template rebind <Waiter>::other a (m_alloc);
for(;;)
{
Waiter* const waiter (state->unused.pop_front());
if (waiter == nullptr)
break;
a.destroy (waiter);
a.deallocate (waiter, 1);
}
}
/** Returns the percentage of time the queue is using the CPU. */
double getUtilizaton () const
{ return m_cpuMeter.getUtilizaton(); }
/** Returns the allocator associated with the container. */
allocator_type get_allocator() const
{
return m_alloc;
}
/** Returns `true` if the current thread is processing events.
If the current thread of execution is inside a call to run,
run_one, poll, or poll_one, this function returns `true`.
*/
bool is_service_thread() const
{ return s_service.get() == this; }
/** Run the handler on a service thread.
If the current thread of execution is a service thread then this
function wil dispatch the handler on the caller's thread before
returning.
The function signature of the handler must be:
@code
void handler();
@endcode
*/
template <typename Handler>
void dispatch (BEAST_MOVE_ARG(Handler) handler)
{
if (is_service_thread())
{
handler();
}
else
{
typename Allocator::template rebind <ItemType <Handler> >::other a (m_alloc);
enqueue (new (a.allocate (1))
ItemType <Handler> (BEAST_MOVE_CAST(Handler)(handler)));
}
}
/** Request the handler to run on a service thread.
This returns immediately, even if the current thread of execution is
a service thread.
The function signature of the handler must be:
@code
void handler();
@endcode
*/
template <typename Handler>
void post (BEAST_MOVE_ARG(Handler) handler)
{
typename Allocator::template rebind <ItemType <Handler> >::other a (m_alloc);
enqueue (new (a.allocate (1))
ItemType <Handler> (BEAST_MOVE_CAST(Handler)(handler)));
}
/** Run the event loop to execute ready handlers.
This runs handlers that are ready to run, without blocking, until
there are no more handlers ready or the service queue has been stopped.
@return The number of handlers that were executed.
*/
std::size_t poll ()
{ return ServiceQueueBase::poll(); }
/** Run the event loop to execute at most one ready handler.
This will run zero or one handlers, without blocking, depending on
whether or not there is handler immediately ready to run.
@return The number of handlers that were executed.
*/
std::size_t poll_one ()
{ return ServiceQueueBase::poll_one(); }
/** Runs the queue's processing loop.
The current thread of execution becomes a service thread. This call
blocks until there is no more work remaining.
@return The number of handlers that were executed.
*/
std::size_t run ()
{ return ServiceQueueBase::run(); }
/** Runs the queue's processing loop to execute at most one handler.
@return The number of handlers that were executed.
*/
std::size_t run_one ()
{ return ServiceQueueBase::run_one(); }
/** Stop the queue's processing loop.
All threads executing run or run_one will return as soon as possible.
Future calls to run, run_one, poll, or poll_one will return immediately
until reset is called.
@see reset
*/
void stop()
{ return ServiceQueueBase::stop(); }
/** Returns `true` if the queue has been stopped.
When a queue is stopped, calls to run, run_one, poll, or poll_one will
return immediately without invoking any handlers.
*/
bool stopped() const
{ return ServiceQueueBase::stopped(); }
/** Reset the queue after a stop.
This allows the event loop to be restarted. This may not be called while
there are any threads currently executing the run, run_one, poll, or
poll_one functions, or undefined behavior will result.
*/
void reset()
{ return ServiceQueueBase::reset(); }
private:
// Dispatch a single queued handler if possible.
// Returns the number of handlers dispatched (0 or 1)
//
std::size_t dequeue ()
{
if (stopped())
return 0;
Item* item (nullptr);
{
SharedState::Access state (m_state);
if (state->handlers.empty())
return 0;
item = &state->handlers.front();
state->handlers.erase (
state->handlers.iterator_to (*item));
}
(*item)();
typename Allocator::template rebind <uint8>::other a (m_alloc);
std::size_t const size (item->size());
item->~Item();
a.deallocate (reinterpret_cast<uint8*>(item), size);
return 1;
}
// Create a new Waiter
Waiter* new_waiter()
{
typename Allocator::template rebind <Waiter>::other a (m_alloc);
return new (a.allocate (1)) Waiter;
}
Allocator m_alloc;
};
typedef ServiceQueueType <std::allocator <char> > ServiceQueue;
}
#endif

272
beast/threads/SharedData.h Normal file
View File

@@ -0,0 +1,272 @@
//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
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.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef BEAST_THREADS_SHAREDDATA_H_INCLUDED
#define BEAST_THREADS_SHAREDDATA_H_INCLUDED
#include "RecursiveMutex.h"
#include "SharedMutexAdapter.h"
namespace beast {
/** Structured, multi-threaded access to a shared state.
This container combines locking semantics with data access semantics to
create an alternative to the typical synchronization method of first
acquiring a lock and then accessing data members.
With this container, access to the underlying data is only possible after
first acquiring a lock. The steps of acquiring the lock and obtaining
a const or non-const reference to the data are combined into one
RAII style operation.
There are three types of access:
- Access
Provides access to the shared state via a non-const reference or pointer.
Access acquires a unique lock on the mutex associated with the
container.
- ConstAccess
Provides access to the shared state via a const reference or pointer.
ConstAccess acquires a shared lock on the mutex associated with the
container.
- ConstUnlockedAccess
Provides access to the shared state via a const reference or pointer.
No locking is performed. It is the callers responsibility to ensure that
the operation is synchronized. This can be useful for diagnostics or
assertions, or for when it is known that no other threads can access
the data.
- UnlockedAccess
Provides access to the shared state via a reference or pointer.
No locking is performed. It is the callers responsibility to ensure that
the operation is synchronized. This can be useful for diagnostics or
assertions, or for when it is known that no other threads can access
the data.
Usage example:
@code
struct State
{
int value1;
String value2;
};
typedef SharedData <State> SharedState;
SharedState m_state;
void readExample ()
{
SharedState::ConstAccess state (m_state);
print (state->value1); // read access
print (state->value2); // read access
state->value1 = 42; // write disallowed: compile error
}
void writeExample ()
{
SharedState::Access state (m_state);
state->value2 = "Label"; // write access, allowed
}
@endcode
Requirements for Value:
Constructible
Destructible
Requirements for SharedMutexType:
X is SharedMutexType, a is an instance of X:
X a; DefaultConstructible
X::LockGuardType Names a type that implements the LockGuard concept.
X::SharedLockGuardType Names a type that implements the SharedLockGuard concept.
@tparam Value The type which the container holds.
@tparam SharedMutexType The type of shared mutex to use.
*/
template <typename Value, class SharedMutexType =
SharedMutexAdapter <RecursiveMutex> >
class SharedData : public Uncopyable
{
private:
typedef typename SharedMutexType::LockGuardType LockGuardType;
typedef typename SharedMutexType::SharedLockGuardType SharedLockGuardType;
public:
typedef Value ValueType;
class Access;
class ConstAccess;
class UnlockedAccess;
class ConstUnlockedAccess;
/** Create a shared data container.
Up to 8 parameters can be specified in the constructor. These parameters
are forwarded to the corresponding constructor in Data. If no
constructor in Data matches the parameter list, a compile error is
generated.
*/
/** @{ */
SharedData () { }
template <class T1>
explicit SharedData (T1 t1)
: m_value (t1) { }
template <class T1, class T2>
SharedData (T1 t1, T2 t2)
: m_value (t1, t2) { }
template <class T1, class T2, class T3>
SharedData (T1 t1, T2 t2, T3 t3)
: m_value (t1, t2, t3) { }
template <class T1, class T2, class T3, class T4>
SharedData (T1 t1, T2 t2, T3 t3, T4 t4)
: m_value (t1, t2, t3, t4) { }
template <class T1, class T2, class T3, class T4, class T5>
SharedData (T1 t1, T2 t2, T3 t3, T4 t4, T5 t5)
: m_value (t1, t2, t3, t4, t5) { }
template <class T1, class T2, class T3, class T4, class T5, class T6>
SharedData (T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6)
: m_value (t1, t2, t3, t4, t5, t6) { }
template <class T1, class T2, class T3, class T4, class T5, class T6, class T7>
SharedData (T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7) : m_value (t1, t2, t3, t4, t5, t6, t7) { }
template <class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8>
SharedData (T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8)
: m_value (t1, t2, t3, t4, t5, t6, t7, t8) { }
/** @} */
private:
Value m_value;
SharedMutexType m_mutex;
};
//------------------------------------------------------------------------------
/** Provides non-const access to the contents of a SharedData.
This acquires a unique lock on the underlying mutex.
*/
template <class Data, class SharedMutexType>
class SharedData <Data, SharedMutexType>::Access : public Uncopyable
{
public:
explicit Access (SharedData& state)
: m_state (state)
, m_lock (m_state.m_mutex)
{ }
Data const& get () const { return m_state.m_value; }
Data const& operator* () const { return get (); }
Data const* operator-> () const { return &get (); }
Data& get () { return m_state.m_value; }
Data& operator* () { return get (); }
Data* operator-> () { return &get (); }
private:
SharedData& m_state;
typename SharedData::LockGuardType m_lock;
};
//------------------------------------------------------------------------------
/** Provides const access to the contents of a SharedData.
This acquires a shared lock on the underlying mutex.
*/
template <class Data, class SharedMutexType>
class SharedData <Data, SharedMutexType>::ConstAccess : public Uncopyable
{
public:
/** Create a ConstAccess from the specified SharedData */
explicit ConstAccess (SharedData const volatile& state)
: m_state (const_cast <SharedData const&> (state))
, m_lock (m_state.m_mutex)
{ }
Data const& get () const { return m_state.m_value; }
Data const& operator* () const { return get (); }
Data const* operator-> () const { return &get (); }
private:
SharedData const& m_state;
typename SharedData::SharedLockGuardType m_lock;
};
//------------------------------------------------------------------------------
/** Provides const access to the contents of a SharedData.
This acquires a shared lock on the underlying mutex.
*/
template <class Data, class SharedMutexType>
class SharedData <Data, SharedMutexType>::ConstUnlockedAccess : public Uncopyable
{
public:
/** Create an UnlockedAccess from the specified SharedData */
explicit ConstUnlockedAccess (SharedData const volatile& state)
: m_state (const_cast <SharedData const&> (state))
{ }
Data const& get () const { return m_state.m_value; }
Data const& operator* () const { return get (); }
Data const* operator-> () const { return &get (); }
private:
SharedData const& m_state;
};
//------------------------------------------------------------------------------
/** Provides access to the contents of a SharedData.
This acquires a shared lock on the underlying mutex.
*/
template <class Data, class SharedMutexType>
class SharedData <Data, SharedMutexType>::UnlockedAccess : public Uncopyable
{
public:
/** Create an UnlockedAccess from the specified SharedData */
explicit UnlockedAccess (SharedData& state)
: m_state (state)
{ }
Data const& get () const { return m_state.m_value; }
Data const& operator* () const { return get (); }
Data const* operator-> () const { return &get (); }
Data& get () { return m_state.m_value; }
Data& operator* () { return get (); }
Data* operator-> () { return &get (); }
private:
SharedData& m_state;
};
}
#endif

View File

@@ -0,0 +1,52 @@
//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
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.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef BEAST_THREADS_SHAREDLOCKGUARD_H_INCLUDED
#define BEAST_THREADS_SHAREDLOCKGUARD_H_INCLUDED
#include "../Uncopyable.h"
namespace beast
{
/** A scoped container that acquires a shared lock. */
template <typename Mutex>
class SharedLockGuard : public Uncopyable
{
public:
typedef Mutex MutexType;
explicit SharedLockGuard (Mutex const& mutex)
: m_mutex (mutex)
{
m_mutex.lock_shared();
}
~SharedLockGuard ()
{
m_mutex.unlock_shared();
}
private:
Mutex const& m_mutex;
};
}
#endif

View File

@@ -0,0 +1,67 @@
//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
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.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef BEAST_THREADS_SHAREDMUTEXADAPTER_H_INCLUDED
#define BEAST_THREADS_SHAREDMUTEXADAPTER_H_INCLUDED
#include "LockGuard.h"
#include "SharedLockGuard.h"
namespace beast {
/** Adapts a regular Lockable to conform to the SharedMutex concept.
Shared locks become unique locks with this interface. Two threads may not
simultaneously acquire ownership of the lock. Typically the Mutex template
parameter will be a CriticalSection.
*/
template <class Mutex>
class SharedMutexAdapter
{
public:
typedef Mutex MutexType;
typedef LockGuard <SharedMutexAdapter> LockGuardType;
typedef SharedLockGuard <SharedMutexAdapter> SharedLockGuardType;
void lock() const
{
m_mutex.lock();
}
void unlock() const
{
m_mutex.unlock();
}
void lock_shared() const
{
m_mutex.lock();
}
void unlock_shared() const
{
m_mutex.unlock();
}
private:
Mutex mutable m_mutex;
};
}
#endif

97
beast/threads/SpinLock.h Normal file
View File

@@ -0,0 +1,97 @@
//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
Portions of this file are from JUCE.
Copyright (c) 2013 - Raw Material Software Ltd.
Please visit http://www.juce.com
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.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef BEAST_THREADS_SPINLOCK_H_INCLUDED
#define BEAST_THREADS_SPINLOCK_H_INCLUDED
#include "../Atomic.h"
#include "LockGuard.h"
#include "UnlockGuard.h"
namespace beast {
//==============================================================================
/**
A simple spin-lock class that can be used as a simple, low-overhead mutex for
uncontended situations.
Note that unlike a CriticalSection, this type of lock is not re-entrant, and may
be less efficient when used it a highly contended situation, but it's very small and
requires almost no initialisation.
It's most appropriate for simple situations where you're only going to hold the
lock for a very brief time.
@see CriticalSection
*/
class BEAST_API SpinLock : public Uncopyable
{
public:
/** Provides the type of scoped lock to use for locking a SpinLock. */
typedef LockGuard <SpinLock> ScopedLockType;
/** Provides the type of scoped unlocker to use with a SpinLock. */
typedef UnlockGuard <SpinLock> ScopedUnlockType;
inline SpinLock() noexcept {}
inline ~SpinLock() noexcept {}
/** Acquires the lock.
This will block until the lock has been successfully acquired by this thread.
Note that a SpinLock is NOT re-entrant, and is not smart enough to know whether the
caller thread already has the lock - so if a thread tries to acquire a lock that it
already holds, this method will never return!
It's strongly recommended that you never call this method directly - instead use the
ScopedLockType class to manage the locking using an RAII pattern instead.
*/
void enter() const noexcept;
/** Attempts to acquire the lock, returning true if this was successful. */
inline bool tryEnter() const noexcept
{
return m_lock.compareAndSetBool (1, 0);
}
/** Releases the lock. */
inline void exit() const noexcept
{
bassert (m_lock.value == 1); // Agh! Releasing a lock that isn't currently held!
m_lock = 0;
}
void lock () const
{ enter(); }
void unlock () const
{ exit(); }
bool try_lock () const
{ return tryEnter(); }
private:
//==============================================================================
mutable Atomic<int> m_lock;
};
}
#endif

327
beast/threads/Stoppable.h Normal file
View File

@@ -0,0 +1,327 @@
//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
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.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef BEAST_THREADS_STOPPABLE_H_INCLUDED
#define BEAST_THREADS_STOPPABLE_H_INCLUDED
#include "../Atomic.h"
#include "../intrusive/LockFreeStack.h"
#include "../utility/Journal.h"
#include "WaitableEvent.h"
namespace beast {
class RootStoppable;
/** Provides an interface for starting and stopping.
A common method of structuring server or peer to peer code is to isolate
conceptual portions of functionality into individual classes, aggregated
into some larger "application" or "core" object which holds all the parts.
Frequently, these components are dependent on each other in unavoidably
complex ways. They also often use threads and perform asynchronous i/o
operations involving sockets or other operating system objects. The process
of starting and stopping such a system can be complex. This interface
provides a set of behaviors for ensuring that the start and stop of a
composite application-style object is well defined.
Upon the initialization of the composite object these steps are peformed:
1. Construct sub-components.
These are all typically derived from Stoppable. There can be a deep
hierarchy: Stoppable objects may themselves have Stoppable child
objects. This captures the relationship of dependencies.
2. prepare()
Because some components may depend on others, preparatory steps require
that all objects be first constructed. The prepare step calls all
Stoppable objects in the tree starting from the leaves and working up
to the root. In this stage we are guaranteed that all objects have been
constructed and are in a well-defined state.
3. onPrepare()
This override is called for all Stoppable objects in the hierarchy
during the prepare stage. Objects are called from the bottom up.
It is guaranteed that all child Stoppable objects have already been
prepared when this is called.
4. start()
At this point all sub-components have been constructed and prepared,
so it should be safe for them to be started. While some Stoppable
objects may do nothing in their start function, others will start
threads or call asynchronous i/o initiating functions like timers or
sockets.
5. onStart()
This override is called for all Stoppable objects in the hierarchy
during the start stage. Objects are called from the bottom up.
It is guaranteed that all child Stoppable objects have already been
started when this is called.
This is the sequence of events involved in stopping:
6. stopAsync() [optional]
This notifies the root Stoppable and all its children that a stop is
requested.
7. stop()
This first calls stopAsync(), and then blocks on each child Stoppable in
the in the tree from the bottom up, until the Stoppable indicates it has
stopped. This will usually be called from the main thread of execution
when some external signal indicates that the process should stop. For
example, an RPC 'stop' command, or a SIGINT POSIX signal.
8. onStop()
This override is called for the root Stoppable and all its children when
stopAsync() is called. Derived classes should cancel pending I/O and
timers, signal that threads should exit, queue cleanup jobs, and perform
any other necessary final actions in preparation for exit.
9. onChildrenStopped()
This override is called when all the children have stopped. This informs
the Stoppable that there should not be any more dependents making calls
into its member functions. A Stoppable that has no children will still
have this function called.
10. stopped()
The derived class calls this function to inform the Stoppable API that
it has completed the stop. This unblocks the caller of stop().
For stoppables which are only considered stopped when all of their
children have stopped, and their own internal logic indicates a stop, it
will be necessary to perform special actions in onChildrenStopped(). The
funtion areChildrenStopped() can be used after children have stopped,
but before the Stoppable logic itself has stopped, to determine if the
stoppable's logic is a true stop.
Pseudo code for this process is as follows:
@code
// Returns `true` if derived logic has stopped.
//
// When the logic stops, logicProcessingStop() is no longer called.
// If children are still active we need to wait until we get a
// notification that the children have stopped.
//
bool logicHasStopped ();
// Called when children have stopped
void onChildrenStopped ()
{
// We have stopped when the derived logic stops and children stop.
if (logicHasStopped)
stopped();
}
// derived-specific logic that executes periodically
void logicProcessingStep ()
{
// process
// ...
// now see if we've stopped
if (logicHasStopped() && areChildrenStopped())
stopped();
}
@endcode
Derived class that manage one or more threads should typically notify
those threads in onStop that they should exit. In the thread function,
when the last thread is about to exit it would call stopped().
@note A Stoppable may not be restarted.
*/
/** @{ */
class Stoppable
{
protected:
Stoppable (char const* name, RootStoppable& root);
public:
/** Create the Stoppable. */
Stoppable (char const* name, Stoppable& parent);
/** Destroy the Stoppable. */
virtual ~Stoppable ();
/** Returns `true` if the stoppable should stop. */
bool isStopping () const;
/** Returns `true` if the requested stop has completed. */
bool isStopped () const;
/** Returns `true` if all children have stopped. */
bool areChildrenStopped () const;
/** Called by derived classes to indicate that the stoppable has stopped. */
void stopped ();
/** Override called during preparation.
Since all other Stoppable objects in the tree have already been
constructed, this provides an opportunity to perform initialization which
depends on calling into other Stoppable objects.
This call is made on the same thread that called prepare().
The default implementation does nothing.
Guaranteed to only be called once.
*/
virtual void onPrepare ();
/** Override called during start. */
virtual void onStart ();
/** Override called when the stop notification is issued.
The call is made on an unspecified, implementation-specific thread.
onStop and onChildrenStopped will never be called concurrently, across
all Stoppable objects descended from the same root, inclusive of the
root.
It is safe to call isStopping, isStopped, and areChildrenStopped from
within this function; The values returned will always be valid and never
change during the callback.
The default implementation simply calls stopped(). This is applicable
when the Stoppable has a trivial stop operation (or no stop operation),
and we are merely using the Stoppable API to position it as a dependency
of some parent service.
Thread safety:
May not block for long periods.
Guaranteed only to be called once.
Must be safe to call from any thread at any time.
*/
virtual void onStop ();
/** Override called when all children have stopped.
The call is made on an unspecified, implementation-specific thread.
onStop and onChildrenStopped will never be called concurrently, across
all Stoppable objects descended from the same root, inclusive of the
root.
It is safe to call isStopping, isStopped, and areChildrenStopped from
within this function; The values returned will always be valid and never
change during the callback.
The default implementation does nothing.
Thread safety:
May not block for long periods.
Guaranteed only to be called once.
Must be safe to call from any thread at any time.
*/
virtual void onChildrenStopped ();
private:
friend class RootStoppable;
struct Child;
typedef LockFreeStack <Child> Children;
struct Child : Children::Node
{
Child (Stoppable* stoppable_) : stoppable (stoppable_)
{
}
Stoppable* stoppable;
};
void prepareRecursive ();
void startRecursive ();
void stopAsyncRecursive ();
void stopRecursive (Journal journal);
protected:
char const* m_name;
RootStoppable& m_root;
Child m_child;
bool volatile m_stopped;
bool volatile m_childrenStopped;
Children m_children;
WaitableEvent m_stoppedEvent;
};
//------------------------------------------------------------------------------
class RootStoppable : public Stoppable
{
public:
explicit RootStoppable (char const* name);
~RootStoppable ();
bool isStopping() const;
/** Prepare all contained Stoppable objects.
This calls onPrepare for all Stoppable objects in the tree.
Calls made after the first have no effect.
Thread safety:
May be called from any thread.
*/
void prepare ();
/** Start all contained Stoppable objects.
The default implementation does nothing.
Calls made after the first have no effect.
Thread safety:
May be called from any thread.
*/
void start ();
/** Notify a root stoppable and children to stop, and block until stopped.
Has no effect if the stoppable was already notified.
This blocks until the stoppable and all of its children have stopped.
Thread safety:
Safe to call from any thread not associated with a Stoppable.
*/
void stop (Journal journal = Journal());
/** Notify a root stoppable and children to stop, without waiting.
Has no effect if the stoppable was already notified.
Thread safety:
Safe to call from any thread at any time.
*/
void stopAsync ();
private:
Atomic <int> m_prepared;
Atomic <int> m_started;
Atomic <int> m_calledStop;
Atomic <int> m_calledStopAsync;
};
/** @} */
}
#endif

294
beast/threads/Thread.h Normal file
View File

@@ -0,0 +1,294 @@
//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
Portions of this file are from JUCE.
Copyright (c) 2013 - Raw Material Software Ltd.
Please visit http://www.juce.com
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.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef BEAST_THREADS_THREAD_H_INCLUDED
#define BEAST_THREADS_THREAD_H_INCLUDED
#include "../utility/LeakChecked.h"
#include "RecursiveMutex.h"
#include "WaitableEvent.h"
namespace beast {
//==============================================================================
/**
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.
This class also contains some thread-related static methods, such
as sleep(), yield(), getCurrentThreadId() etc.
@see CriticalSection, WaitableEvent, Process, ThreadWithProgressWindow,
MessageManagerLock
*/
class BEAST_API Thread : LeakChecked <Thread>, public Uncopyable
{
public:
//==============================================================================
/**
Creates a thread.
When first created, the thread is not running. Use the startThread()
method to start it.
*/
explicit Thread (const String& threadName);
/** Destructor.
If the thread has not been stopped first, this will generate a fatal error.
*/
virtual ~Thread();
//==============================================================================
/** Must be implemented to perform the thread's actual code.
Remember that the thread must regularly check the threadShouldExit()
method whilst running, and if this returns true it should return from
the run() method as soon as possible to avoid being forcibly killed.
@see threadShouldExit, startThread
*/
virtual void run() = 0;
//==============================================================================
// Thread control functions..
/** Starts the thread running.
This will start the thread's run() method.
(if it's already started, startThread() won't do anything).
@see stopThread
*/
void startThread();
/** Starts the 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.
@see startThread, setPriority
*/
void startThread (int priority);
/** Attempts to stop the thread running.
This method will cause the threadShouldExit() method to return true
and call notify() in case the thread is currently waiting.
Hopefully the thread will then respond to this by exiting cleanly, and
the stopThread method will wait for a given time-period for this to
happen.
If the thread is stuck and fails to respond after the time-out, it gets
forcibly killed, which is a very bad thing to happen, as it could still
be holding locks, etc. which are needed by other parts of your program.
@param timeOutMilliseconds The number of milliseconds to wait for the
thread to finish before killing it by force. A negative
value in here will wait forever.
@see signalThreadShouldExit, threadShouldExit, waitForThreadToExit, isThreadRunning
@returns true if the thread exits, or false if the timeout expires first.
*/
bool stopThread (int timeOutMilliseconds = -1);
/** Stop the thread without blocking.
This calls signalThreadShouldExit followed by notify.
*/
void stopThreadAsync ();
//==============================================================================
/** Returns true if the thread is currently active */
bool isThreadRunning() const;
/** Sets a flag to tell the thread it should stop.
Calling this means that the threadShouldExit() method will then return true.
The thread should be regularly checking this to see whether it should exit.
If your thread makes use of wait(), you might want to call notify() after calling
this method, to interrupt any waits that might be in progress, and allow it
to reach a point where it can exit.
@see threadShouldExit
@see waitForThreadToExit
*/
void signalThreadShouldExit();
/** Checks whether the thread has been told to stop running.
Threads need to check this regularly, and if it returns true, they should
return from their run() method at the first possible opportunity.
@see signalThreadShouldExit
*/
inline bool threadShouldExit() const { return shouldExit; }
/** Waits for the thread to stop.
This will waits until isThreadRunning() is false or until a timeout expires.
@param timeOutMilliseconds the time to wait, in milliseconds. If this value
is less than zero, it will wait forever.
@returns true if the thread exits, or false if the timeout expires first.
*/
bool waitForThreadToExit (int timeOutMilliseconds = -1) const;
//==============================================================================
/** 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.
*/
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.
This will only have an effect next time the thread is started - i.e. if the
thread is already running when called, it'll have no effect.
@see setCurrentThreadAffinityMask
*/
void setAffinityMask (uint32 affinityMask);
/** Changes the affinity mask for the caller thread.
This will change the affinity mask for the thread that calls this static method.
@see setAffinityMask
*/
static void setCurrentThreadAffinityMask (uint32 affinityMask);
//==============================================================================
// this can be called from any thread that needs to pause..
static void BEAST_CALLTYPE sleep (int milliseconds);
/** Yields the calling thread's current time-slot. */
static void BEAST_CALLTYPE yield();
//==============================================================================
/** Makes the thread wait for a notification.
This puts the thread to sleep until either the timeout period expires, or
another thread calls the notify() method to wake it up.
A negative time-out value means that the method will wait indefinitely.
@returns true if the event has been signalled, false if the timeout expires.
*/
bool wait (int timeOutMilliseconds = -1) const;
/** Wakes up the thread.
If the thread has called the wait() method, this will wake it up.
@see wait
*/
void notify() const;
//==============================================================================
/** A value type used for thread IDs.
@see getCurrentThreadId(), getThreadId()
*/
typedef void* ThreadID;
/** Returns an id that identifies the caller thread.
To find the ID of a particular thread object, use getThreadId().
@returns a unique identifier that identifies the calling thread.
@see getThreadId
*/
static ThreadID getCurrentThreadId();
/** Finds the thread object that is currently running.
Note that the main UI thread (or other non-Beast threads) don't have a Thread
object associated with them, so this will return 0.
*/
static Thread* getCurrentThread();
/** Returns the ID of this thread.
That means the ID of this thread object - not of the thread that's calling the method.
This can change when the thread is started and stopped, and will be invalid if the
thread's not actually running.
@see getCurrentThreadId
*/
ThreadID getThreadId() const noexcept { return threadId; }
/** Returns the name of the thread.
This is the name that gets set in the constructor.
*/
const String& getThreadName() const { return threadName; }
/** Changes the name of the caller thread.
Different OSes may place different length or content limits on this name.
*/
static void setCurrentThreadName (const String& newThreadName);
private:
//==============================================================================
const String threadName;
void* volatile threadHandle;
ThreadID threadId;
RecursiveMutex startStopLock;
WaitableEvent startSuspensionEvent, defaultEvent;
int threadPriority;
uint32 affinityMask;
bool volatile shouldExit;
#ifndef DOXYGEN
friend void BEAST_API beast_threadEntryPoint (void*);
#endif
void launchThread();
void closeThreadHandle();
void killThread();
void threadEntryPoint();
static bool setThreadPriority (void*, int);
};
}
#endif

View File

@@ -0,0 +1,197 @@
//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
Portions of this file are from JUCE.
Copyright (c) 2013 - Raw Material Software Ltd.
Please visit http://www.juce.com
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.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef BEAST_THREADS_THREADLOCALVALUE_H_INCLUDED
#define BEAST_THREADS_THREADLOCALVALUE_H_INCLUDED
#include "../Config.h"
#include "SpinLock.h"
#include "Thread.h"
namespace beast {
// (NB: on win32, native thread-locals aren't possible in a dynamically loaded DLL in XP).
#if ! ((BEAST_MSVC && (BEAST_64BIT || ! defined (BeastPlugin_PluginCode))) \
|| (BEAST_MAC && BEAST_CLANG && defined (MAC_OS_X_VERSION_10_7) \
&& MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7))
#define BEAST_NO_COMPILER_THREAD_LOCAL 1
#endif
//==============================================================================
/**
Provides cross-platform support for thread-local objects.
This class holds an internal list of objects of the templated type, keeping
an instance for each thread that requests one. The first time a thread attempts
to access its value, an object is created and added to the list for that thread.
Typically, you'll probably want to create a static instance of a ThreadLocalValue
object, or hold one within a singleton.
The templated class for your value could be a primitive type, or any class that
has a default constructor and copy operator.
When a thread no longer needs to use its value, it can call releaseCurrentThreadStorage()
to allow the storage to be re-used by another thread. If a thread exits without calling
this method, the object storage will be left allocated until the ThreadLocalValue object
is deleted.
*/
template <typename Type>
class ThreadLocalValue : public Uncopyable
{
public:
/** */
ThreadLocalValue() noexcept
{
}
/** Destructor.
When this object is deleted, all the value objects for all threads will be deleted.
*/
~ThreadLocalValue()
{
#if BEAST_NO_COMPILER_THREAD_LOCAL
for (ObjectHolder* o = first.value; o != nullptr;)
{
ObjectHolder* const next = o->next;
delete o;
o = next;
}
#endif
}
/** Returns a reference to this thread's instance of the value.
Note that the first time a thread tries to access the value, an instance of the
value object will be created - so if your value's class has a non-trivial
constructor, be aware that this method could invoke it.
*/
Type& operator*() const noexcept { return get(); }
/** Returns a pointer to this thread's instance of the value.
Note that the first time a thread tries to access the value, an instance of the
value object will be created - so if your value's class has a non-trivial
constructor, be aware that this method could invoke it.
*/
operator Type*() const noexcept { return &get(); }
/** Accesses a method or field of the value object.
Note that the first time a thread tries to access the value, an instance of the
value object will be created - so if your value's class has a non-trivial
constructor, be aware that this method could invoke it.
*/
Type* operator->() const noexcept { return &get(); }
/** Assigns a new value to the thread-local object. */
ThreadLocalValue& operator= (const Type& newValue) { get() = newValue; return *this; }
/** Returns a reference to this thread's instance of the value.
Note that the first time a thread tries to access the value, an instance of the
value object will be created - so if your value's class has a non-trivial
constructor, be aware that this method could invoke it.
*/
Type& get() const noexcept
{
#if BEAST_NO_COMPILER_THREAD_LOCAL
const Thread::ThreadID threadId = Thread::getCurrentThreadId();
for (ObjectHolder* o = first.get(); o != nullptr; o = o->next)
if (o->threadId == threadId)
return o->object;
for (ObjectHolder* o = first.get(); o != nullptr; o = o->next)
{
if (o->threadId == nullptr)
{
{
SpinLock::ScopedLockType sl (lock);
if (o->threadId != nullptr)
continue;
o->threadId = threadId;
}
o->object = Type();
return o->object;
}
}
ObjectHolder* const newObject = new ObjectHolder (threadId);
do
{
newObject->next = first.get();
}
while (! first.compareAndSetBool (newObject, newObject->next));
return newObject->object;
#elif BEAST_MAC
static __thread Type object;
return object;
#elif BEAST_MSVC
static __declspec(thread) Type object;
return object;
#endif
}
/** Called by a thread before it terminates, to allow this class to release
any storage associated with the thread.
*/
void releaseCurrentThreadStorage()
{
#if BEAST_NO_COMPILER_THREAD_LOCAL
const Thread::ThreadID threadId = Thread::getCurrentThreadId();
for (ObjectHolder* o = first.get(); o != nullptr; o = o->next)
{
if (o->threadId == threadId)
{
SpinLock::ScopedLockType sl (lock);
o->threadId = nullptr;
}
}
#endif
}
private:
//==============================================================================
#if BEAST_NO_COMPILER_THREAD_LOCAL
struct ObjectHolder : public Uncopyable
{
ObjectHolder (const Thread::ThreadID& tid)
: threadId (tid), object()
{}
Thread::ThreadID threadId;
ObjectHolder* next;
Type object;
};
mutable Atomic<ObjectHolder*> first;
SpinLock lock;
#endif
};
}
#endif

27
beast/threads/Threads.cpp Normal file
View File

@@ -0,0 +1,27 @@
//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
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.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include "BeastConfig.h"
#include "impl/Atomic.cpp"
#include "impl/RecursiveMutex.cpp"
#include "impl/ServiceQueue.cpp"
#include "impl/Stoppable.cpp"
#include "impl/Thread.cpp"
#include "impl/WaitableEvent.cpp"

View File

@@ -0,0 +1,55 @@
//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
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.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef BEAST_THREADS_TRYLOCKGUARD_H_INCLUDED
#define BEAST_THREADS_TRYLOCKGUARD_H_INCLUDED
#include "../Uncopyable.h"
namespace beast {
template <typename Mutex>
class TryLockGuard : public Uncopyable
{
public:
typedef Mutex MutexType;
explicit TryLockGuard (Mutex const& mutex)
: m_mutex (mutex)
, m_owns_lock (m_mutex.try_lock())
{
}
~TryLockGuard ()
{
if (m_owns_lock)
m_mutex.unlock();
}
bool owns_lock() const
{ return m_owns_lock; }
private:
Mutex const& m_mutex;
bool m_owns_lock;
};
}
#endif

View File

@@ -0,0 +1,50 @@
//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
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.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef BEAST_THREADS_UNLOCKGUARD_H_INCLUDED
#define BEAST_THREADS_UNLOCKGUARD_H_INCLUDED
#include "../Uncopyable.h"
namespace beast {
template <typename Mutex>
class UnlockGuard : public Uncopyable
{
public:
typedef Mutex MutexType;
explicit UnlockGuard (Mutex const& mutex)
: m_mutex (mutex)
{
m_mutex.unlock();
}
~UnlockGuard ()
{
m_mutex.lock();
}
private:
Mutex const& m_mutex;
};
}
#endif

View File

@@ -0,0 +1,124 @@
//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
Portions of this file are from JUCE.
Copyright (c) 2013 - Raw Material Software Ltd.
Please visit http://www.juce.com
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.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef BEAST_THREADS_WAITABLEEVENT_H_INCLUDED
#define BEAST_THREADS_WAITABLEEVENT_H_INCLUDED
#include "../Config.h"
#include "../Uncopyable.h"
#if ! BEAST_WINDOWS
#include <pthread.h>
#endif
namespace beast {
/** Allows threads to wait for events triggered by other threads.
A thread can call wait() on a WaitableEvent, and this will suspend the
calling thread until another thread wakes it up by calling the signal()
method.
*/
class WaitableEvent
: public Uncopyable
//, LeakChecked <WaitableEvent> // VFALCO TODO Move LeakChecked to beast/
{
public:
//==============================================================================
/** Creates a WaitableEvent object.
@param manualReset If this is false, the event will be reset automatically when the wait()
method is called. If manualReset is true, then once the event is signalled,
the only way to reset it will be by calling the reset() method.
@param initiallySignaled If this is true then the event will be signaled when
the constructor returns.
*/
explicit WaitableEvent (bool manualReset = false, bool initiallySignaled = false);
/** Destructor.
If other threads are waiting on this object when it gets deleted, this
can cause nasty errors, so be careful!
*/
~WaitableEvent();
//==============================================================================
/** Suspends the calling thread until the event has been signalled.
This will wait until the object's signal() method is called by another thread,
or until the timeout expires.
After the event has been signalled, this method will return true and if manualReset
was set to false in the WaitableEvent's constructor, then the event will be reset.
@param timeOutMilliseconds the maximum time to wait, in milliseconds. A negative
value will cause it to wait forever.
@returns true if the object has been signalled, false if the timeout expires first.
@see signal, reset
*/
/** @{ */
bool wait () const; // wait forever
// VFALCO TODO Change wait() to seconds instead of millis
bool wait (int timeOutMilliseconds) const; // DEPRECATED
/** @} */
//==============================================================================
/** Wakes up any threads that are currently waiting on this object.
If signal() is called when nothing is waiting, the next thread to call wait()
will return immediately and reset the signal.
If the WaitableEvent is manual reset, all current and future threads that wait upon this
object will be woken, until reset() is explicitly called.
If the WaitableEvent is automatic reset, and one or more threads is waiting upon the object,
then one of them will be woken up. If no threads are currently waiting, then the next thread
to call wait() will be woken up. As soon as a thread is woken, the signal is automatically
reset.
@see wait, reset
*/
void signal() const;
//==============================================================================
/** Resets the event to an unsignalled state.
If it's not already signalled, this does nothing.
*/
void reset() const;
private:
#if BEAST_WINDOWS
void* handle;
#else
mutable pthread_cond_t condition;
mutable pthread_mutex_t mutex;
mutable bool triggered;
mutable bool manualReset;
#endif
};
}
#endif

View File

@@ -0,0 +1,133 @@
//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
Portions of this file are from JUCE.
Copyright (c) 2013 - Raw Material Software Ltd.
Please visit http://www.juce.com
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.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include "../../../modules/beast_core/beast_core.h" // for UnitTest
namespace beast {
class AtomicTests : public UnitTest
{
public:
AtomicTests() : UnitTest ("Atomic", "beast") {}
template <typename Type>
void testFloat ()
{
Atomic<Type> a, b;
a = (Type) 21;
memoryBarrier();
/* These are some simple test cases to check the atomics - let me know
if any of these assertions fail on your system!
*/
expect (a.get() == (Type) 21);
expect (a.compareAndSetValue ((Type) 100, (Type) 50) == (Type) 21);
expect (a.get() == (Type) 21);
expect (a.compareAndSetValue ((Type) 101, a.get()) == (Type) 21);
expect (a.get() == (Type) 101);
expect (! a.compareAndSetBool ((Type) 300, (Type) 200));
expect (a.get() == (Type) 101);
expect (a.compareAndSetBool ((Type) 200, a.get()));
expect (a.get() == (Type) 200);
expect (a.exchange ((Type) 300) == (Type) 200);
expect (a.get() == (Type) 300);
b = a;
expect (b.get() == a.get());
}
template <typename Type>
void testInteger ()
{
Atomic<Type> a, b;
a.set ((Type) 10);
expect (a.value == (Type) 10);
expect (a.get() == (Type) 10);
a += (Type) 15;
expect (a.get() == (Type) 25);
memoryBarrier();
a -= (Type) 5;
expect (a.get() == (Type) 20);
expect (++a == (Type) 21);
++a;
expect (--a == (Type) 21);
expect (a.get() == (Type) 21);
memoryBarrier();
testFloat <Type> ();
}
void runTest()
{
beginTestCase ("Misc");
char a1[7];
expect (numElementsInArray(a1) == 7);
int a2[3];
expect (numElementsInArray(a2) == 3);
expect (ByteOrder::swap ((uint16) 0x1122) == 0x2211);
expect (ByteOrder::swap ((uint32) 0x11223344) == 0x44332211);
expect (ByteOrder::swap ((uint64) literal64bit (0x1122334455667788)) == literal64bit (0x8877665544332211));
beginTestCase ("int");
testInteger <int> ();
beginTestCase ("unsigned int");
testInteger <unsigned int> ();
beginTestCase ("int32");
testInteger <int32> ();
beginTestCase ("uint32");
testInteger <uint32> ();
beginTestCase ("long");
testInteger <long> ();
beginTestCase ("void*");
testInteger <void*> ();
beginTestCase ("int*");
testInteger <int*> ();
beginTestCase ("float");
testFloat <float> ();
#if ! BEAST_64BIT_ATOMICS_UNAVAILABLE // 64-bit intrinsics aren't available on some old platforms
beginTestCase ("int64");
testInteger <int64> ();
beginTestCase ("uint64");
testInteger <uint64> ();
beginTestCase ("double");
testFloat <double> ();
#endif
}
};
static AtomicTests atomicTests;
}

View File

@@ -0,0 +1,111 @@
//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
Portions of this file are from JUCE.
Copyright (c) 2013 - Raw Material Software Ltd.
Please visit http://www.juce.com
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.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include "../RecursiveMutex.h"
#if BEAST_WINDOWS
#include "../StaticAssert.h"
#include <Windows.h>
#undef check
#undef direct
#undef max
#undef min
#undef TYPE_BOOL
namespace beast {
RecursiveMutex::RecursiveMutex()
{
// (just to check the MS haven't changed this structure and broken things...)
#if BEAST_VC7_OR_EARLIER
static_bassert (sizeof (CRITICAL_SECTION) <= 24);
#else
static_bassert (sizeof (CRITICAL_SECTION) <= sizeof (section));
#endif
InitializeCriticalSection ((CRITICAL_SECTION*) section);
}
RecursiveMutex::~RecursiveMutex()
{
DeleteCriticalSection ((CRITICAL_SECTION*) section);
}
void RecursiveMutex::lock() const
{
EnterCriticalSection ((CRITICAL_SECTION*) section);
}
void RecursiveMutex::unlock() const
{
LeaveCriticalSection ((CRITICAL_SECTION*) section);
}
bool RecursiveMutex::try_lock() const
{
return TryEnterCriticalSection ((CRITICAL_SECTION*) section) != FALSE;
}
}
#else
namespace beast {
RecursiveMutex::RecursiveMutex()
{
pthread_mutexattr_t atts;
pthread_mutexattr_init (&atts);
pthread_mutexattr_settype (&atts, PTHREAD_MUTEX_RECURSIVE);
#if ! BEAST_ANDROID
pthread_mutexattr_setprotocol (&atts, PTHREAD_PRIO_INHERIT);
#endif
pthread_mutex_init (&mutex, &atts);
pthread_mutexattr_destroy (&atts);
}
RecursiveMutex::~RecursiveMutex()
{
pthread_mutex_destroy (&mutex);
}
void RecursiveMutex::lock() const
{
pthread_mutex_lock (&mutex);
}
void RecursiveMutex::unlock() const
{
pthread_mutex_unlock (&mutex);
}
bool RecursiveMutex::try_lock() const
{
return pthread_mutex_trylock (&mutex) == 0;
}
}
#endif

View File

@@ -0,0 +1,465 @@
//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
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.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include "../ServiceQueue.h"
#include "../../../modules/beast_core/beast_core.h" // for UnitTest
namespace beast {
namespace detail {
//------------------------------------------------------------------------------
class ServiceQueueBase::ScopedServiceThread : public List <ScopedServiceThread>::Node
{
public:
explicit ScopedServiceThread (ServiceQueueBase* queue)
: m_saved (ServiceQueueBase::s_service.get())
{
ServiceQueueBase::s_service.get() = queue;
}
~ScopedServiceThread()
{
ServiceQueueBase::s_service.get() = m_saved;
}
private:
ServiceQueueBase* m_saved;
};
//------------------------------------------------------------------------------
ServiceQueueBase::ServiceQueueBase()
{
}
ServiceQueueBase::~ServiceQueueBase()
{
}
std::size_t ServiceQueueBase::poll ()
{
CPUMeter::ScopedActiveTime interval (m_cpuMeter);
std::size_t total (0);
ScopedServiceThread thread (this);
for (;;)
{
std::size_t const n (dequeue());
if (! n)
break;
total += n;
}
return total;
}
std::size_t ServiceQueueBase::poll_one ()
{
CPUMeter::ScopedActiveTime interval (m_cpuMeter);
ScopedServiceThread thread (this);
return dequeue();
}
std::size_t ServiceQueueBase::run ()
{
std::size_t total (0);
ScopedServiceThread thread (this);
while (! stopped())
{
{
CPUMeter::ScopedActiveTime interval (m_cpuMeter);
total += poll ();
}
{
CPUMeter::ScopedIdleTime interval (m_cpuMeter);
wait ();
}
}
return total;
}
std::size_t ServiceQueueBase::run_one ()
{
std::size_t n;
ScopedServiceThread (this);
for (;;)
{
{
CPUMeter::ScopedActiveTime interval (m_cpuMeter);
n = poll_one();
if (n != 0)
break;
}
{
CPUMeter::ScopedIdleTime interval (m_cpuMeter);
wait();
}
}
return n;
}
void ServiceQueueBase::stop ()
{
SharedState::Access state (m_state);
m_stopped.set (1);
for(;;)
{
Waiter* waiting (state->waiting.pop_front());
if (waiting == nullptr)
break;
waiting->signal();
}
}
void ServiceQueueBase::reset()
{
// Must be stopped
bassert (m_stopped.get () != 0);
m_stopped.set (0);
}
// Block on the event if there are no items
// in the queue and we are not stopped.
//
void ServiceQueueBase::wait ()
{
Waiter* waiter (nullptr);
{
SharedState::Access state (m_state);
if (stopped ())
return;
if (! state->handlers.empty())
return;
waiter = state->unused.pop_front();
if (! waiter)
waiter = new_waiter();
state->waiting.push_front (waiter);
}
waiter->wait();
// Waiter got taken off the waiting list
{
SharedState::Access state (m_state);
state->unused.push_front (waiter);
}
}
void ServiceQueueBase::enqueue (Item* item)
{
Waiter* waiter;
{
SharedState::Access state (m_state);
state->handlers.push_back (*item);
// Signal a Waiter if one exists
waiter = state->waiting.pop_front();
}
if (waiter != nullptr)
waiter->signal();
}
// A thread can only be blocked on one ServiceQueue so we store the pointer
// to which ServiceQueue it is blocked on to determine if the thread belongs
// to that queue.
//
ThreadLocalValue <ServiceQueueBase*> ServiceQueueBase::s_service;
}
//------------------------------------------------------------------------------
namespace detail
{
//------------------------------------------------------------------------------
class ServiceQueueTimingTests
: public UnitTest
{
public:
class Stopwatch
{
public:
Stopwatch () { start(); }
void start () { m_startTime = Time::getHighResolutionTicks (); }
double getElapsed ()
{
int64 const now = Time::getHighResolutionTicks();
return Time::highResolutionTicksToSeconds (now - m_startTime);
}
private:
int64 m_startTime;
};
static int const callsPerThread = 50000;
//--------------------------------------------------------------------------
template <typename ServiceType>
struct Consumer : Thread
{
ServiceType& m_service;
Random m_random;
String m_string;
Consumer (int id, int64 seedValue, ServiceType& service)
: Thread ("C#" + String::fromNumber (id))
, m_service (service)
, m_random (seedValue)
{ startThread(); }
~Consumer ()
{ stopThread(); }
static Consumer*& thread()
{
static ThreadLocalValue <Consumer*> local;
return local.get();
}
static void stop_one ()
{ thread()->signalThreadShouldExit(); }
static void handler ()
{ thread()->do_handler(); }
void do_handler()
{
String const s (String::fromNumber (m_random.nextInt()));
m_string += s;
if (m_string.length() > 100)
m_string = String::empty;
}
void run ()
{
thread() = this;
while (! threadShouldExit())
m_service.run_one();
}
};
//--------------------------------------------------------------------------
template <typename ServiceType>
struct Producer : Thread
{
ServiceType& m_service;
Random m_random;
String m_string;
Producer (int id, int64 seedValue, ServiceType& service)
: Thread ("P#" + String::fromNumber (id))
, m_service (service)
, m_random (seedValue)
{ }
~Producer ()
{ stopThread(); }
void run ()
{
for (std::size_t i = 0; i < callsPerThread; ++i)
{
String const s (String::fromNumber (m_random.nextInt()));
m_string += s;
if (m_string.length() > 100)
m_string = String::empty;
m_service.dispatch (bind (&Consumer<ServiceType>::handler));
}
}
};
//--------------------------------------------------------------------------
template <typename Allocator>
void testThreads (std::size_t nConsumers, std::size_t nProducers)
{
beginTestCase (String::fromNumber (nConsumers) + " consumers, " +
String::fromNumber (nProducers) + " producers, " +
"Allocator = " + std::string(typeid(Allocator).name()));
typedef ServiceQueueType <Allocator> ServiceType;
ServiceType service (nConsumers);
std::vector <ScopedPointer <Consumer <ServiceType> > > consumers;
std::vector <ScopedPointer <Producer <ServiceType> > > producers;
consumers.reserve (nConsumers);
producers.reserve (nProducers);
for (std::size_t i = 0; i < nConsumers; ++i)
consumers.push_back (new Consumer <ServiceType> (i + 1,
random().nextInt64(), service));
for (std::size_t i = 0; i < nProducers; ++i)
producers.push_back (new Producer <ServiceType> (i + 1,
random().nextInt64(), service));
Stopwatch t;
for (std::size_t i = 0; i < producers.size(); ++i)
producers[i]->startThread();
for (std::size_t i = 0; i < producers.size(); ++i)
producers[i]->waitForThreadToExit();
for (std::size_t i = 0; i < consumers.size(); ++i)
service.dispatch (bind (&Consumer <ServiceType>::stop_one));
for (std::size_t i = 0; i < consumers.size(); ++i)
consumers[i]->waitForThreadToExit();
double const seconds (t.getElapsed());
logMessage (String (seconds, 2) + " seconds");
pass();
}
void runTest()
{
#if 1
testThreads <std::allocator<char> > (1, 1);
testThreads <std::allocator<char> > (1, 4);
testThreads <std::allocator<char> > (1, 16);
testThreads <std::allocator<char> > (4, 1);
testThreads <std::allocator<char> > (8, 16);
#endif
#if 0
testThreads <detail::ServiceQueueAllocator<char> > (1, 1);
testThreads <detail::ServiceQueueAllocator<char> > (1, 4);
testThreads <detail::ServiceQueueAllocator<char> > (1, 16);
testThreads <detail::ServiceQueueAllocator<char> > (4, 1);
testThreads <detail::ServiceQueueAllocator<char> > (8, 16);
#endif
}
ServiceQueueTimingTests () : UnitTest ("ServiceQueueTiming", "beast", runManual)
{
}
};
static ServiceQueueTimingTests serviceQueueTimingTests;
//------------------------------------------------------------------------------
class ServiceQueueTests
: public UnitTest
{
public:
struct ServiceThread : Thread
{
Random m_random;
ServiceQueue& m_service;
String m_string;
ServiceThread (int id, int64 seedValue,
ServiceQueue& service)
: Thread ("#" + String::fromNumber (id))
, m_random (seedValue)
, m_service (service)
{
startThread();
}
~ServiceThread ()
{
stopThread();
}
static ServiceThread*& thread()
{
static ThreadLocalValue <ServiceThread*> local;
return local.get();
}
static void stop_one ()
{
thread()->signalThreadShouldExit();
}
static void handler ()
{
thread()->do_handler();
}
void do_handler()
{
#if 1
String const s (String::fromNumber (m_random.nextInt()));
m_string += s;
if (m_string.length() > 100)
m_string = String::empty;
#endif
}
void run ()
{
thread() = this;
while (! threadShouldExit())
m_service.run_one();
}
};
static int const callsPerThread = 10000;
void testThreads (std::size_t n)
{
beginTestCase (String::fromNumber (n) + " threads");
ServiceQueue service (n);
std::vector <ScopedPointer <ServiceThread> > threads;
threads.reserve (n);
for (std::size_t i = 0; i < n; ++i)
threads.push_back (new ServiceThread (i + 1,
random().nextInt64(), service));
for (std::size_t i = n * callsPerThread; i; --i)
service.dispatch (bind (&ServiceThread::handler));
for (std::size_t i = 0; i < threads.size(); ++i)
service.dispatch (bind (&ServiceThread::stop_one));
for (std::size_t i = 0; i < threads.size(); ++i)
threads[i]->waitForThreadToExit();
pass();
}
void runTest()
{
testThreads (1);
testThreads (4);
testThreads (16);
}
ServiceQueueTests () : UnitTest ("ServiceQueue", "beast")
{
}
};
static ServiceQueueTests serviceQueueTests;
}
}

View File

@@ -0,0 +1,198 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
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.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include "../Stoppable.h"
namespace beast {
Stoppable::Stoppable (char const* name, RootStoppable& root)
: m_name (name)
, m_root (root)
, m_child (this)
, m_stopped (false)
, m_childrenStopped (false)
{
}
Stoppable::Stoppable (char const* name, Stoppable& parent)
: m_name (name)
, m_root (parent.m_root)
, m_child (this)
, m_stopped (false)
, m_childrenStopped (false)
{
// Must not have stopping parent.
bassert (! parent.isStopping());
parent.m_children.push_front (&m_child);
}
Stoppable::~Stoppable ()
{
// Children must be stopped.
bassert (m_childrenStopped);
}
bool Stoppable::isStopping() const
{
return m_root.isStopping();
}
bool Stoppable::isStopped () const
{
return m_stopped;
}
bool Stoppable::areChildrenStopped () const
{
return m_childrenStopped;
}
void Stoppable::stopped ()
{
m_stoppedEvent.signal();
}
void Stoppable::onPrepare ()
{
}
void Stoppable::onStart ()
{
}
void Stoppable::onStop ()
{
stopped();
}
void Stoppable::onChildrenStopped ()
{
}
//------------------------------------------------------------------------------
void Stoppable::prepareRecursive ()
{
for (Children::const_iterator iter (m_children.cbegin ());
iter != m_children.cend(); ++iter)
iter->stoppable->prepareRecursive ();
onPrepare ();
}
void Stoppable::startRecursive ()
{
onStart ();
for (Children::const_iterator iter (m_children.cbegin ());
iter != m_children.cend(); ++iter)
iter->stoppable->startRecursive ();
}
void Stoppable::stopAsyncRecursive ()
{
onStop ();
for (Children::const_iterator iter (m_children.cbegin ());
iter != m_children.cend(); ++iter)
iter->stoppable->stopAsyncRecursive ();
}
void Stoppable::stopRecursive (Journal journal)
{
// Block on each child from the bottom of the tree up.
//
for (Children::const_iterator iter (m_children.cbegin ());
iter != m_children.cend(); ++iter)
iter->stoppable->stopRecursive (journal);
// if we get here then all children have stopped
//
memoryBarrier ();
m_childrenStopped = true;
onChildrenStopped ();
// Now block on this Stoppable.
//
bool const timedOut (! m_stoppedEvent.wait (1 * 1000)); // milliseconds
if (timedOut)
{
journal.warning << "Waiting for '" << m_name << "' to stop";
m_stoppedEvent.wait ();
}
// once we get here, we know the stoppable has stopped.
m_stopped = true;
}
//------------------------------------------------------------------------------
RootStoppable::RootStoppable (char const* name)
: Stoppable (name, *this)
{
}
RootStoppable::~RootStoppable ()
{
}
bool RootStoppable::isStopping() const
{
return m_calledStopAsync.get() != 0;
}
void RootStoppable::prepare ()
{
if (! m_prepared.compareAndSetBool (1, 0))
return;
prepareRecursive ();
}
void RootStoppable::start ()
{
// Courtesy call to prepare.
if (m_prepared.compareAndSetBool (1, 0))
prepareRecursive ();
if (! m_started.compareAndSetBool (1, 0))
return;
startRecursive ();
}
void RootStoppable::stop (Journal journal)
{
if (! m_calledStop.compareAndSetBool (1, 0))
{
journal.warning << "Stoppable::stop called again";
return;
}
stopAsync ();
stopRecursive (journal);
}
void RootStoppable::stopAsync ()
{
if (! m_calledStopAsync.compareAndSetBool (1, 0))
return;
stopAsyncRecursive ();
}
}

View File

@@ -0,0 +1,599 @@
//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
Portions of this file are from JUCE.
Copyright (c) 2013 - Raw Material Software Ltd.
Please visit http://www.juce.com
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.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include "../Thread.h"
namespace beast {
Thread::Thread (const String& threadName_)
: threadName (threadName_),
threadHandle (nullptr),
threadId (0),
threadPriority (5),
affinityMask (0),
shouldExit (false)
{
}
Thread::~Thread()
{
/* If your thread class's destructor has been called without first stopping the thread, that
means that this partially destructed object is still performing some work - and that's
probably a Bad Thing!
To avoid this type of nastiness, always make sure you call stopThread() before or during
your subclass's destructor.
*/
check_precondition (! isThreadRunning());
stopThread ();
}
//==============================================================================
// Use a ref-counted object to hold this shared data, so that it can outlive its static
// shared pointer when threads are still running during static shutdown.
struct CurrentThreadHolder : public SharedObject
{
CurrentThreadHolder() noexcept {}
typedef SharedPtr <CurrentThreadHolder> Ptr;
ThreadLocalValue<Thread*> value;
};
static char currentThreadHolderLock [sizeof (SpinLock)]; // (statically initialised to zeros).
static SpinLock* castToSpinLockWithoutAliasingWarning (void* s)
{
return static_cast<SpinLock*> (s);
}
static CurrentThreadHolder::Ptr getCurrentThreadHolder()
{
static CurrentThreadHolder::Ptr currentThreadHolder;
SpinLock::ScopedLockType lock (*castToSpinLockWithoutAliasingWarning (currentThreadHolderLock));
if (currentThreadHolder == nullptr)
currentThreadHolder = new CurrentThreadHolder();
return currentThreadHolder;
}
void Thread::threadEntryPoint()
{
const CurrentThreadHolder::Ptr currentThreadHolder (getCurrentThreadHolder());
currentThreadHolder->value = this;
if (threadName.isNotEmpty())
setCurrentThreadName (threadName);
if (startSuspensionEvent.wait (10000))
{
bassert (getCurrentThreadId() == threadId);
if (affinityMask != 0)
setCurrentThreadAffinityMask (affinityMask);
run();
}
currentThreadHolder->value.releaseCurrentThreadStorage();
closeThreadHandle();
}
// used to wrap the incoming call from the platform-specific code
void BEAST_API beast_threadEntryPoint (void* userData)
{
static_cast <Thread*> (userData)->threadEntryPoint();
}
//==============================================================================
void Thread::startThread()
{
const RecursiveMutex::ScopedLockType sl (startStopLock);
shouldExit = false;
if (threadHandle == nullptr)
{
launchThread();
setThreadPriority (threadHandle, threadPriority);
startSuspensionEvent.signal();
}
}
void Thread::startThread (const int priority)
{
const RecursiveMutex::ScopedLockType sl (startStopLock);
if (threadHandle == nullptr)
{
threadPriority = priority;
startThread();
}
else
{
setPriority (priority);
}
}
bool Thread::isThreadRunning() const
{
return threadHandle != nullptr;
}
Thread* Thread::getCurrentThread()
{
return getCurrentThreadHolder()->value.get();
}
//==============================================================================
void Thread::signalThreadShouldExit()
{
shouldExit = true;
}
bool Thread::waitForThreadToExit (const int timeOutMilliseconds) const
{
// Doh! So how exactly do you expect this thread to wait for itself to stop??
bassert (getThreadId() != getCurrentThreadId() || getCurrentThreadId() == 0);
const uint32 timeoutEnd = Time::getMillisecondCounter() + (uint32) timeOutMilliseconds;
while (isThreadRunning())
{
if (timeOutMilliseconds >= 0 && Time::getMillisecondCounter() > timeoutEnd)
return false;
sleep (2);
}
return true;
}
bool Thread::stopThread (const int timeOutMilliseconds)
{
bool cleanExit = true;
// agh! You can't stop the thread that's calling this method! How on earth
// would that work??
bassert (getCurrentThreadId() != getThreadId());
const RecursiveMutex::ScopedLockType sl (startStopLock);
if (isThreadRunning())
{
signalThreadShouldExit();
notify();
if (timeOutMilliseconds != 0)
{
cleanExit = waitForThreadToExit (timeOutMilliseconds);
}
if (isThreadRunning())
{
bassert (! cleanExit);
// very bad karma if this point is reached, as there are bound to be
// locks and events left in silly states when a thread is killed by force..
killThread();
threadHandle = nullptr;
threadId = 0;
cleanExit = false;
}
else
{
cleanExit = true;
}
}
return cleanExit;
}
void Thread::stopThreadAsync ()
{
const RecursiveMutex::ScopedLockType sl (startStopLock);
if (isThreadRunning())
{
signalThreadShouldExit();
notify();
}
}
//==============================================================================
bool Thread::setPriority (const int 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 RecursiveMutex::ScopedLockType sl (startStopLock);
if (setThreadPriority (threadHandle, newPriority))
{
threadPriority = newPriority;
return true;
}
return false;
}
bool Thread::setCurrentThreadPriority (const int newPriority)
{
return setThreadPriority (0, newPriority);
}
void Thread::setAffinityMask (const uint32 newAffinityMask)
{
affinityMask = newAffinityMask;
}
//==============================================================================
bool Thread::wait (const int timeOutMilliseconds) const
{
return defaultEvent.wait (timeOutMilliseconds);
}
void Thread::notify() const
{
defaultEvent.signal();
}
//==============================================================================
// This is here so we dont have circular includes
//
void SpinLock::enter() const noexcept
{
if (! tryEnter())
{
for (int i = 20; --i >= 0;)
if (tryEnter())
return;
while (! tryEnter())
Thread::yield();
}
}
}
//------------------------------------------------------------------------------
#if BEAST_WINDOWS
#include <windows.h>
#include <process.h>
#include <tchar.h>
namespace beast {
HWND beast_messageWindowHandle = 0; // (this is used by other parts of the codebase)
void BEAST_API beast_threadEntryPoint (void*);
static unsigned int __stdcall threadEntryProc (void* userData)
{
if (beast_messageWindowHandle != 0)
AttachThreadInput (GetWindowThreadProcessId (beast_messageWindowHandle, 0),
GetCurrentThreadId(), TRUE);
beast_threadEntryPoint (userData);
_endthreadex (0);
return 0;
}
void Thread::launchThread()
{
unsigned int newThreadId;
threadHandle = (void*) _beginthreadex (0, 0, &threadEntryProc, this, 0, &newThreadId);
threadId = (ThreadID) newThreadId;
}
void Thread::closeThreadHandle()
{
CloseHandle ((HANDLE) threadHandle);
threadId = 0;
threadHandle = 0;
}
void Thread::killThread()
{
if (threadHandle != 0)
{
#if BEAST_DEBUG
OutputDebugStringA ("** Warning - Forced thread termination **\n");
#endif
TerminateThread (threadHandle, 0);
}
}
void Thread::setCurrentThreadName (const String& name)
{
#if BEAST_DEBUG && BEAST_MSVC
struct
{
DWORD dwType;
LPCSTR szName;
DWORD dwThreadID;
DWORD dwFlags;
} info;
info.dwType = 0x1000;
info.szName = name.toUTF8();
info.dwThreadID = GetCurrentThreadId();
info.dwFlags = 0;
__try
{
RaiseException (0x406d1388 /*MS_VC_EXCEPTION*/, 0, sizeof (info) / sizeof (ULONG_PTR), (ULONG_PTR*) &info);
}
__except (EXCEPTION_CONTINUE_EXECUTION)
{}
#else
(void) name;
#endif
}
Thread::ThreadID 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 == 0)
handle = GetCurrentThread();
return SetThreadPriority (handle, pri) != FALSE;
}
void Thread::setCurrentThreadAffinityMask (const uint32 affinityMask)
{
SetThreadAffinityMask (GetCurrentThread(), affinityMask);
}
struct SleepEvent
{
SleepEvent() noexcept
: handle (CreateEvent (nullptr, FALSE, FALSE,
#if BEAST_DEBUG
_T("BEAST Sleep Event")))
#else
nullptr))
#endif
{}
~SleepEvent() noexcept
{
CloseHandle (handle);
handle = 0;
}
HANDLE handle;
};
static SleepEvent sleepEvent;
void BEAST_CALLTYPE Thread::sleep (const int millisecs)
{
if (millisecs >= 10 || sleepEvent.handle == 0)
{
Sleep ((DWORD) millisecs);
}
else
{
// unlike Sleep() this is guaranteed to return to the current thread after
// the time expires, so we'll use this for short waits, which are more likely
// to need to be accurate
WaitForSingleObject (sleepEvent.handle, (DWORD) millisecs);
}
}
void Thread::yield()
{
Sleep (0);
}
}
//------------------------------------------------------------------------------
#else
#include <time.h>
#if BEAST_BSD
// ???
#else
# include <sys/prctl.h>
#endif
namespace beast {
void BEAST_CALLTYPE Thread::sleep (int millisecs)
{
struct timespec time;
time.tv_sec = millisecs / 1000;
time.tv_nsec = (millisecs % 1000) * 1000000;
nanosleep (&time, nullptr);
}
void BEAST_API beast_threadEntryPoint (void*);
extern "C" void* threadEntryProc (void*);
extern "C" void* threadEntryProc (void* userData)
{
BEAST_AUTORELEASEPOOL
{
#if BEAST_ANDROID
struct AndroidThreadScope
{
AndroidThreadScope() { threadLocalJNIEnvHolder.attach(); }
~AndroidThreadScope() { threadLocalJNIEnvHolder.detach(); }
};
const AndroidThreadScope androidEnv;
#endif
beast_threadEntryPoint (userData);
}
return nullptr;
}
void Thread::launchThread()
{
threadHandle = 0;
pthread_t handle = 0;
if (pthread_create (&handle, 0, threadEntryProc, this) == 0)
{
pthread_detach (handle);
threadHandle = (void*) handle;
threadId = (ThreadID) threadHandle;
}
}
void Thread::closeThreadHandle()
{
threadId = 0;
threadHandle = 0;
}
void Thread::killThread()
{
if (threadHandle != 0)
{
#if BEAST_ANDROID
bassertfalse; // pthread_cancel not available!
#else
pthread_cancel ((pthread_t) threadHandle);
#endif
}
}
void Thread::setCurrentThreadName (const String& name)
{
#if BEAST_IOS || (BEAST_MAC && defined (MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)
BEAST_AUTORELEASEPOOL
{
[[NSThread currentThread] setName: beastStringToNS (name)];
}
#elif BEAST_LINUX
#if (__GLIBC__ * 1000 + __GLIBC_MINOR__) >= 2012
pthread_setname_np (pthread_self(), name.toRawUTF8());
#else
prctl (PR_SET_NAME, name.toRawUTF8(), 0, 0, 0);
#endif
#endif
}
bool Thread::setThreadPriority (void* handle, int priority)
{
struct sched_param param;
int policy;
priority = blimit (0, 10, priority);
if (handle == nullptr)
handle = (void*) pthread_self();
if (pthread_getschedparam ((pthread_t) handle, &policy, &param) != 0)
return false;
policy = priority == 0 ? SCHED_OTHER : SCHED_RR;
const int minPriority = sched_get_priority_min (policy);
const int maxPriority = sched_get_priority_max (policy);
param.sched_priority = ((maxPriority - minPriority) * priority) / 10 + minPriority;
return pthread_setschedparam ((pthread_t) handle, policy, &param) == 0;
}
Thread::ThreadID Thread::getCurrentThreadId()
{
return (ThreadID) pthread_self();
}
void Thread::yield()
{
sched_yield();
}
//==============================================================================
/* Remove this macro if you're having problems compiling the cpu affinity
calls (the API for these has changed about quite a bit in various Linux
versions, and a lot of distros seem to ship with obsolete versions)
*/
#if defined (CPU_ISSET) && ! defined (SUPPORT_AFFINITIES)
#define SUPPORT_AFFINITIES 1
#endif
void Thread::setCurrentThreadAffinityMask (const uint32 affinityMask)
{
#if SUPPORT_AFFINITIES
cpu_set_t affinity;
CPU_ZERO (&affinity);
for (int i = 0; i < 32; ++i)
if ((affinityMask & (1 << i)) != 0)
CPU_SET (i, &affinity);
/*
N.B. If this line causes a compile error, then you've probably not got the latest
version of glibc installed.
If you don't want to update your copy of glibc and don't care about cpu affinities,
then you can just disable all this stuff by setting the SUPPORT_AFFINITIES macro to 0.
*/
sched_setaffinity (getpid(), sizeof (cpu_set_t), &affinity);
sched_yield();
#else
/* affinities aren't supported because either the appropriate header files weren't found,
or the SUPPORT_AFFINITIES macro was turned off
*/
bassertfalse;
(void) affinityMask;
#endif
}
}
//------------------------------------------------------------------------------
#endif

View File

@@ -0,0 +1,168 @@
//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
Portions of this file are from JUCE.
Copyright (c) 2013 - Raw Material Software Ltd.
Please visit http://www.juce.com
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.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include "../WaitableEvent.h"
#if BEAST_WINDOWS
#include <Windows.h>
#undef check
#undef direct
#undef max
#undef min
#undef TYPE_BOOL
namespace beast {
WaitableEvent::WaitableEvent (const bool manualReset, bool initiallySignaled)
: handle (CreateEvent (0, manualReset ? TRUE : FALSE, initiallySignaled ? TRUE : FALSE, 0))
{
}
WaitableEvent::~WaitableEvent()
{
CloseHandle (handle);
}
void WaitableEvent::signal() const
{
SetEvent (handle);
}
void WaitableEvent::reset() const
{
ResetEvent (handle);
}
bool WaitableEvent::wait () const
{
return WaitForSingleObject (handle, INFINITE) == WAIT_OBJECT_0;
}
bool WaitableEvent::wait (const int timeOutMs) const
{
if (timeOutMs >= 0)
return WaitForSingleObject (handle,
(DWORD) timeOutMs) == WAIT_OBJECT_0;
return wait ();
}
}
#else
namespace beast {
WaitableEvent::WaitableEvent (const bool useManualReset, bool initiallySignaled)
: triggered (false), manualReset (useManualReset)
{
pthread_cond_init (&condition, 0);
pthread_mutexattr_t atts;
pthread_mutexattr_init (&atts);
#if ! BEAST_ANDROID
pthread_mutexattr_setprotocol (&atts, PTHREAD_PRIO_INHERIT);
#endif
pthread_mutex_init (&mutex, &atts);
if (initiallySignaled)
signal ();
}
WaitableEvent::~WaitableEvent()
{
pthread_cond_destroy (&condition);
pthread_mutex_destroy (&mutex);
}
bool WaitableEvent::wait () const
{
return wait (-1);
}
bool WaitableEvent::wait (const int timeOutMillisecs) const
{
pthread_mutex_lock (&mutex);
if (! triggered)
{
if (timeOutMillisecs < 0)
{
do
{
pthread_cond_wait (&condition, &mutex);
}
while (! triggered);
}
else
{
struct timeval now;
gettimeofday (&now, 0);
struct timespec time;
time.tv_sec = now.tv_sec + (timeOutMillisecs / 1000);
time.tv_nsec = (now.tv_usec + ((timeOutMillisecs % 1000) * 1000)) * 1000;
if (time.tv_nsec >= 1000000000)
{
time.tv_nsec -= 1000000000;
time.tv_sec++;
}
do
{
if (pthread_cond_timedwait (&condition, &mutex, &time) == ETIMEDOUT)
{
pthread_mutex_unlock (&mutex);
return false;
}
}
while (! triggered);
}
}
if (! manualReset)
triggered = false;
pthread_mutex_unlock (&mutex);
return true;
}
void WaitableEvent::signal() const
{
pthread_mutex_lock (&mutex);
triggered = true;
pthread_cond_broadcast (&condition);
pthread_mutex_unlock (&mutex);
}
void WaitableEvent::reset() const
{
pthread_mutex_lock (&mutex);
triggered = false;
pthread_mutex_unlock (&mutex);
}
}
#endif