CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
182 lines
5.8 KiB
C++
182 lines
5.8 KiB
C++
/*
|
|
* Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
#ifndef MONITOR_HPP_
|
|
#define MONITOR_HPP_
|
|
|
|
#include "top.hpp"
|
|
#include "utils/flags.hpp"
|
|
#include <condition_variable>
|
|
#include <mutex>
|
|
#include <atomic>
|
|
#include <tuple>
|
|
#include <utility>
|
|
#include "os/os.hpp"
|
|
|
|
namespace amd {
|
|
|
|
class Monitor {
|
|
public:
|
|
explicit Monitor() {
|
|
waits_.store(0); // 0 waiting thread initially
|
|
notifyState_.store(notifyState::notNotified); // initially not notified
|
|
}
|
|
|
|
//! Try to acquire the lock, return true if successful, false if failed.
|
|
bool tryLock() { return mutex_.try_lock(); }
|
|
|
|
//! Acquire the lock or suspend the calling thread.
|
|
void lock() { mutex_.lock(); }
|
|
|
|
//! Release the lock and wake a single waiting thread if any.
|
|
void unlock() { mutex_.unlock(); }
|
|
|
|
// GCC 12+ emits a false -Wstringop-overflow when it inlines atomic ops on
|
|
// class members through multiple call frames and loses size provenance.
|
|
#if defined(__GNUC__) && !defined(__clang__)
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wstringop-overflow"
|
|
#endif
|
|
|
|
/*! \brief Give up the lock and go to sleep.
|
|
*
|
|
* Calling wait() causes the current thread to go to sleep until
|
|
* another thread calls notify()/notifyAll().
|
|
*
|
|
* \note The monitor must be owned before calling wait().
|
|
*/
|
|
void wait() {
|
|
assert(waits_.load(std::memory_order_acquire) >= 0 && "Error: waits_.load() < 0");
|
|
std::unique_lock lk(mutex_, std::adopt_lock);
|
|
|
|
int c = 0;
|
|
while (unlikely(notifyState_.load(std::memory_order_acquire) == notifyState::allNotified)) {
|
|
lk.unlock();
|
|
// NotifyAll() processing already in progress, don't enter now.
|
|
// The new wait() shoule be processed by next notifyAll().
|
|
if (c < maxReadSpinIter_) {
|
|
Os::spinPause();
|
|
c++;
|
|
}
|
|
// and then SMP friendly
|
|
else {
|
|
Os::yield();
|
|
}
|
|
lk.lock();
|
|
}
|
|
waits_.fetch_add(1, std::memory_order_acq_rel);
|
|
|
|
lk.unlock();
|
|
// fast path
|
|
c = 0;
|
|
while (c < maxCount_ &&
|
|
(notifyState_.load(std::memory_order_acquire) == notifyState::notNotified)) {
|
|
// First, be SMT friendly
|
|
if (c < maxReadSpinIter_) {
|
|
Os::spinPause();
|
|
}
|
|
// and then SMP friendly
|
|
else {
|
|
Os::yield();
|
|
}
|
|
c++;
|
|
}
|
|
assert(c <= maxCount_ && "Error: c > maxCount_");
|
|
|
|
lk.lock();
|
|
|
|
if (c == maxCount_) {
|
|
// In case notify() is called between loop and here
|
|
notifyState expextedNotifyState = notifyState::oneNotified;
|
|
if (notifyState_.load(std::memory_order_acquire) != notifyState::allNotified &&
|
|
!notifyState_.compare_exchange_strong(expextedNotifyState, notifyState::notNotified,
|
|
std::memory_order_acq_rel,
|
|
std::memory_order_acquire)) {
|
|
// Still not notified, so enter slow path
|
|
cv_.wait(lk); // slow path
|
|
expextedNotifyState = notifyState::oneNotified;
|
|
// To reset notifyState::oneNotified to notifyState::notNotified state if notifyState_ is
|
|
// notifyState::oneNotified.
|
|
// This will happen when notify() is called during cv_.wait(lk). Will do nothing
|
|
// if notifyState_ is notifyState::notNoftifed or notifyState::allNotified.
|
|
notifyState_.compare_exchange_strong(expextedNotifyState, notifyState::notNotified,
|
|
std::memory_order_acq_rel, std::memory_order_acquire);
|
|
}
|
|
}
|
|
// the mutex is locked again before exiting...
|
|
lk.release(); // Release the ownership so that the caller should unlock the mutex
|
|
if (waits_.fetch_sub(1, std::memory_order_acq_rel) == 1) {
|
|
// No waiter indicates that notify() or notifyAll() processing has ended
|
|
notifyState_.store(notifyState::notNotified, std::memory_order_release);
|
|
}
|
|
}
|
|
|
|
/*! \brief Wake up a single thread waiting on this monitor.
|
|
*
|
|
* \note The monitor need be owned before calling notify().
|
|
*/
|
|
void notify() {
|
|
// If notifyState_ is notifyState::oneNotified or notifyState::allNotified, this will be
|
|
// skipped.
|
|
if (notifyState_.load(std::memory_order_acquire) == notifyState::notNotified &&
|
|
waits_.load(std::memory_order_acquire) > 0) {
|
|
notifyState_.store(notifyState::oneNotified, std::memory_order_release);
|
|
cv_.notify_one();
|
|
}
|
|
}
|
|
|
|
/*! \brief Wake up all threads that are waiting on this monitor.
|
|
*
|
|
* \note The monitor need be owned before calling notifyAll().
|
|
*/
|
|
void notifyAll() {
|
|
// If notifyState_ is notifyState::allNotified, this will be skipped. So notifyAll()
|
|
// can still be called if notify() is just called as notifyAll() covers notify()
|
|
if (notifyState_.load(std::memory_order_acquire) != notifyState::allNotified &&
|
|
waits_.load(std::memory_order_acquire) > 0) {
|
|
// One notification is enough
|
|
notifyState_.store(notifyState::allNotified, std::memory_order_release);
|
|
cv_.notify_all();
|
|
}
|
|
}
|
|
|
|
#if defined(__GNUC__) && !defined(__clang__)
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
|
|
private:
|
|
std::mutex mutex_;
|
|
|
|
enum class notifyState : uint32_t { notNotified = 0, oneNotified = 1, allNotified = 2 };
|
|
std::condition_variable cv_; //!< The condition variable for sync on the mutex
|
|
std::atomic<int> waits_;
|
|
std::atomic<notifyState> notifyState_;
|
|
const int maxCount_{55}; //!< Max count of spins in wait()
|
|
const int maxReadSpinIter_{50};
|
|
};
|
|
|
|
class ScopedLock : StackObject {
|
|
public:
|
|
explicit ScopedLock(Monitor& lock) : lock_(&lock) { lock_->lock(); }
|
|
|
|
explicit ScopedLock(Monitor* lock) : lock_(lock) {
|
|
if (lock_) {
|
|
lock_->lock();
|
|
}
|
|
}
|
|
|
|
~ScopedLock() {
|
|
if (lock_) lock_->unlock();
|
|
}
|
|
|
|
private:
|
|
Monitor* lock_;
|
|
};
|
|
|
|
} // namespace amd
|
|
|
|
#endif /*MONITOR_HPP_*/
|