You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lokinet/llarp/util/thread/threading.hpp

157 lines
3.3 KiB
C++

#pragma once
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <optional>
#include <shared_mutex>
#include <thread>
#if defined(WIN32) && !defined(__GNUC__)
#include <process.h>
11 months ago
using pid_t = int;
#else
#include <sys/types.h>
#include <unistd.h>
#endif
11 months ago
namespace llarp::util
6 years ago
{
11 months ago
/// a mutex that does nothing
///
/// this exists to convert mutexes that were initially in use (but may no
/// longer be necessary) into no-op placeholders (except in debug mode
/// where they complain loudly when they are actually accessed across
/// different threads; see below).
///
/// the idea is to "turn off" the mutexes and see where they are actually
/// needed.
struct NullMutex
6 years ago
{
#ifdef LOKINET_DEBUG
11 months ago
/// in debug mode, we implement lock() to enforce that any lock is only
/// used from a single thread. the point of this is to identify locks that
/// are actually needed by dying a painful death when used across threads
mutable std::optional<std::thread::id> m_id;
void
lock() const
6 years ago
{
11 months ago
if (!m_id)
{
11 months ago
m_id = std::this_thread::get_id();
}
11 months ago
else if (*m_id != std::this_thread::get_id())
6 years ago
{
11 months ago
std::cerr << "NullMutex " << this << " was used across threads: locked by "
<< std::this_thread::get_id() << " and was previously locked by " << *m_id
<< "\n";
// if you're encountering this abort() call, you may have discovered a
// case where a NullMutex should be reverted to a "real mutex"
std::abort();
6 years ago
}
11 months ago
}
#else
void
lock() const
{}
#endif
// Does nothing; once locked the mutex belongs to that thread forever
void
unlock() const
{}
};
De-abseil, part 2: mutex, locks, (most) time - util::Mutex is now a std::shared_timed_mutex, which is capable of exclusive and shared locks. - util::Lock is still present as a std::lock_guard<util::Mutex>. - the locking annotations are preserved, but updated to the latest supported by clang rather than using abseil's older/deprecated ones. - ACQUIRE_LOCK macro is gone since we don't pass mutexes by pointer into locks anymore (WTF abseil). - ReleasableLock is gone. Instead there are now some llarp::util helper methods to obtain unique and/or shared locks: - `auto lock = util::unique_lock(mutex);` gets an RAII-but-also unlockable object (std::unique_lock<T>, with T inferred from `mutex`). - `auto lock = util::shared_lock(mutex);` gets an RAII shared (i.e. "reader") lock of the mutex. - `auto lock = util::unique_locks(mutex1, mutex2, mutex3);` can be used to atomically lock multiple mutexes at once (returning a tuple of the locks). This are templated on the mutex which makes them a bit more flexible than using a concrete type: they can be used for any type of lockable mutex, not only util::Mutex. (Some of the code here uses them for getting locks around a std::mutex). Until C++17, using the RAII types is painfully verbose: ```C++ // pre-C++17 - needing to figure out the mutex type here is annoying: std::unique_lock<util::Mutex> lock(mutex); // pre-C++17 and even more verbose (but at least the type isn't needed): std::unique_lock<decltype(mutex)> lock(mutex); // our compromise: auto lock = util::unique_lock(mutex); // C++17: std::unique_lock lock(mutex); ``` All of these functions will also warn (under gcc or clang) if you discard the return value. You can also do fancy things like `auto l = util::unique_lock(mutex, std::adopt_lock)` (which lets a lock take over an already-locked mutex). - metrics code is gone, which also removes a big pile of code that was only used by metrics: - llarp::util::Scheduler - llarp::thread::TimerQueue - llarp::util::Stopwatch
5 years ago
11 months ago
/// a lock that does nothing
struct NullLock
{
NullLock(NullMutex& mtx)
De-abseil, part 2: mutex, locks, (most) time - util::Mutex is now a std::shared_timed_mutex, which is capable of exclusive and shared locks. - util::Lock is still present as a std::lock_guard<util::Mutex>. - the locking annotations are preserved, but updated to the latest supported by clang rather than using abseil's older/deprecated ones. - ACQUIRE_LOCK macro is gone since we don't pass mutexes by pointer into locks anymore (WTF abseil). - ReleasableLock is gone. Instead there are now some llarp::util helper methods to obtain unique and/or shared locks: - `auto lock = util::unique_lock(mutex);` gets an RAII-but-also unlockable object (std::unique_lock<T>, with T inferred from `mutex`). - `auto lock = util::shared_lock(mutex);` gets an RAII shared (i.e. "reader") lock of the mutex. - `auto lock = util::unique_locks(mutex1, mutex2, mutex3);` can be used to atomically lock multiple mutexes at once (returning a tuple of the locks). This are templated on the mutex which makes them a bit more flexible than using a concrete type: they can be used for any type of lockable mutex, not only util::Mutex. (Some of the code here uses them for getting locks around a std::mutex). Until C++17, using the RAII types is painfully verbose: ```C++ // pre-C++17 - needing to figure out the mutex type here is annoying: std::unique_lock<util::Mutex> lock(mutex); // pre-C++17 and even more verbose (but at least the type isn't needed): std::unique_lock<decltype(mutex)> lock(mutex); // our compromise: auto lock = util::unique_lock(mutex); // C++17: std::unique_lock lock(mutex); ``` All of these functions will also warn (under gcc or clang) if you discard the return value. You can also do fancy things like `auto l = util::unique_lock(mutex, std::adopt_lock)` (which lets a lock take over an already-locked mutex). - metrics code is gone, which also removes a big pile of code that was only used by metrics: - llarp::util::Scheduler - llarp::thread::TimerQueue - llarp::util::Stopwatch
5 years ago
{
11 months ago
mtx.lock();
De-abseil, part 2: mutex, locks, (most) time - util::Mutex is now a std::shared_timed_mutex, which is capable of exclusive and shared locks. - util::Lock is still present as a std::lock_guard<util::Mutex>. - the locking annotations are preserved, but updated to the latest supported by clang rather than using abseil's older/deprecated ones. - ACQUIRE_LOCK macro is gone since we don't pass mutexes by pointer into locks anymore (WTF abseil). - ReleasableLock is gone. Instead there are now some llarp::util helper methods to obtain unique and/or shared locks: - `auto lock = util::unique_lock(mutex);` gets an RAII-but-also unlockable object (std::unique_lock<T>, with T inferred from `mutex`). - `auto lock = util::shared_lock(mutex);` gets an RAII shared (i.e. "reader") lock of the mutex. - `auto lock = util::unique_locks(mutex1, mutex2, mutex3);` can be used to atomically lock multiple mutexes at once (returning a tuple of the locks). This are templated on the mutex which makes them a bit more flexible than using a concrete type: they can be used for any type of lockable mutex, not only util::Mutex. (Some of the code here uses them for getting locks around a std::mutex). Until C++17, using the RAII types is painfully verbose: ```C++ // pre-C++17 - needing to figure out the mutex type here is annoying: std::unique_lock<util::Mutex> lock(mutex); // pre-C++17 and even more verbose (but at least the type isn't needed): std::unique_lock<decltype(mutex)> lock(mutex); // our compromise: auto lock = util::unique_lock(mutex); // C++17: std::unique_lock lock(mutex); ``` All of these functions will also warn (under gcc or clang) if you discard the return value. You can also do fancy things like `auto l = util::unique_lock(mutex, std::adopt_lock)` (which lets a lock take over an already-locked mutex). - metrics code is gone, which also removes a big pile of code that was only used by metrics: - llarp::util::Scheduler - llarp::thread::TimerQueue - llarp::util::Stopwatch
5 years ago
}
6 years ago
11 months ago
~NullLock()
{
11 months ago
(void)this; // trick clang-tidy
}
};
11 months ago
/// Default mutex type, supporting shared and exclusive locks.
using Mutex = std::shared_timed_mutex;
11 months ago
/// Basic RAII lock type for the default mutex type.
using Lock = std::lock_guard<Mutex>;
11 months ago
class Semaphore
{
private:
std::mutex m_mutex; // protects m_count
size_t m_count;
std::condition_variable m_cv;
11 months ago
public:
Semaphore(size_t count) : m_count(count)
{}
11 months ago
void
notify()
{
{
std::lock_guard<std::mutex> lock(m_mutex);
m_count++;
}
11 months ago
m_cv.notify_one();
}
void
11 months ago
wait()
{
std::unique_lock lock{m_mutex};
m_cv.wait(lock, [this] { return m_count > 0; });
m_count--;
}
11 months ago
bool
waitFor(std::chrono::microseconds timeout)
{
11 months ago
std::unique_lock lock{m_mutex};
if (!m_cv.wait_for(lock, timeout, [this] { return m_count > 0; }))
return false;
m_count--;
return true;
}
};
void
SetThreadName(const std::string& name);
inline pid_t
GetPid()
{
#ifdef WIN32
11 months ago
return _getpid();
#else
11 months ago
return ::getpid();
#endif
11 months ago
}
11 months ago
// type for detecting contention on a resource
struct ContentionKiller
{
template <typename F>
void
TryAccess(F visit) const
{
#if defined(LOKINET_DEBUG)
11 months ago
NullLock lock(_access);
#endif
11 months ago
visit();
}
5 years ago
#if defined(LOKINET_DEBUG)
11 months ago
private:
mutable NullMutex _access;
5 years ago
#endif
11 months ago
};
} // namespace llarp::util