From ff4fbbdc5fa5ada01bd5bc3d8f8004279d7cc6b8 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 24 May 2020 14:39:39 +0200 Subject: Switch from ThreadPool to a simpler thread map for containers. --- src/thread_utils.h | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/thread_utils.h (limited to 'src/thread_utils.h') diff --git a/src/thread_utils.h b/src/thread_utils.h new file mode 100644 index 00000000..fe096a36 --- /dev/null +++ b/src/thread_utils.h @@ -0,0 +1,55 @@ +#ifndef MO2_THREAD_UTILS_H +#define MO2_THREAD_UTILS_H + +#include +#include + +namespace MOShared { + +/** + * @brief Apply the given callable to each element between the two given iterators + * in a parallel way. + * + * The callable should be independent, or properly synchronized, and the source of + * the range should not change during this call. + * + * @param start Beginning of the range. + * @param end End of the range. + * @param callable Callable to apply to every element of the range. See std::invoke + * requirements. Must be copiable. + * @param nThreads Number of threads to use. + * + */ +template +void parallelMap(It begin, It end, Callable callable, std::size_t nThreads) { + std::vector threads(nThreads); + + std::mutex m; + for (auto &thread: threads) { + thread = std::thread([&m, &begin, end, callable]() { + while (true) { + decltype(begin) it; + { + std::scoped_lock lock(m); + if (begin == end) { + break; + } + it = begin++; + } + if (it != end) { + std::invoke(callable, *it); + } + } + }); + } + + + // Join everything: + for (auto& t : threads) { + t.join(); + } +} + +} + +#endif -- cgit v1.3.1 From 001e44fec45be3fe94d5075812547c0277f2e6d1 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 24 May 2020 14:40:44 +0200 Subject: Minor cleaning. --- src/thread_utils.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'src/thread_utils.h') diff --git a/src/thread_utils.h b/src/thread_utils.h index fe096a36..2cb87a8d 100644 --- a/src/thread_utils.h +++ b/src/thread_utils.h @@ -21,10 +21,14 @@ namespace MOShared { * */ template -void parallelMap(It begin, It end, Callable callable, std::size_t nThreads) { +void parallelMap(It begin, It end, Callable callable, std::size_t nThreads) +{ + std::mutex m; std::vector threads(nThreads); - std::mutex m; + // Create the thread: + // - The mutex is only used to fetch/increment the iterator. + // - The callable is copied in each thread to avoid conflicts. for (auto &thread: threads) { thread = std::thread([&m, &begin, end, callable]() { while (true) { @@ -43,7 +47,6 @@ void parallelMap(It begin, It end, Callable callable, std::size_t nThreads) { }); } - // Join everything: for (auto& t : threads) { t.join(); -- cgit v1.3.1 From 0aac4f3291d65ebc1b0bbee744fca347ecef7022 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 24 May 2020 23:32:47 +0200 Subject: Add MemoizedLocked. --- src/thread_utils.h | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) (limited to 'src/thread_utils.h') diff --git a/src/thread_utils.h b/src/thread_utils.h index 2cb87a8d..6c044138 100644 --- a/src/thread_utils.h +++ b/src/thread_utils.h @@ -6,6 +6,49 @@ namespace MOShared { +/** + * Class that can be used to perform thread-safe memoization. + * + * Each instance hold a flag indicating if the current value is up-to-date + * or not. This flag can be reset using `invalidate()`. When the value is queried, + * the flag is checked, and if it is not up-to-date, the given callback is used + * to compute the value. + * + * The computation and update of the value is locked to avoid concurrent modifications. + * + * @tparam T Type of value ot memoized. + * @tparam Fn Type of the callback. + */ +template +struct MemoizedLocked { + + MemoizedLocked(Fn callback, T value = {}) : + m_Fn{ callback }, m_Value{ std::move(value) } { } + + template + T& value(Args&&... args) const { + if (m_NeedUpdating) { + std::scoped_lock lock(m_Mutex); + if (m_NeedUpdating) { + m_Value = std::invoke(m_Fn, std::forward(args)... ); + m_NeedUpdating.store(false); + } + } + return m_Value; + } + + void invalidate() const { + m_NeedUpdating.store(true); + } + +private: + mutable std::mutex m_Mutex; + mutable std::atomic m_NeedUpdating{ true }; + + Fn m_Fn; + mutable T m_Value; +}; + /** * @brief Apply the given callable to each element between the two given iterators * in a parallel way. -- cgit v1.3.1 From 86f516953df9dc80c6726c30f47e15b660a89a4e Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Mon, 25 May 2020 17:56:45 +0200 Subject: Update MemoizedLock to allow more flexible callable. --- src/thread_utils.h | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'src/thread_utils.h') diff --git a/src/thread_utils.h b/src/thread_utils.h index 6c044138..f0067b6a 100644 --- a/src/thread_utils.h +++ b/src/thread_utils.h @@ -1,6 +1,7 @@ #ifndef MO2_THREAD_UTILS_H #define MO2_THREAD_UTILS_H +#include #include #include @@ -19,11 +20,12 @@ namespace MOShared { * @tparam T Type of value ot memoized. * @tparam Fn Type of the callback. */ -template +template > struct MemoizedLocked { - MemoizedLocked(Fn callback, T value = {}) : - m_Fn{ callback }, m_Value{ std::move(value) } { } + template + MemoizedLocked(Callable &&callable, T value = {}) : + m_Fn{ std::forward(callable) }, m_Value{ std::move(value) } { } template T& value(Args&&... args) const { @@ -31,14 +33,14 @@ struct MemoizedLocked { std::scoped_lock lock(m_Mutex); if (m_NeedUpdating) { m_Value = std::invoke(m_Fn, std::forward(args)... ); - m_NeedUpdating.store(false); + m_NeedUpdating = false; } } return m_Value; } - void invalidate() const { - m_NeedUpdating.store(true); + void invalidate() { + m_NeedUpdating = true; } private: -- cgit v1.3.1