Files
rocm-systems/rocclr/runtime/thread/monitor.cpp
T

314 líneas
7.8 KiB
C++
Original Vista normal Histórico

2014-07-04 16:17:05 -04:00
//
// Copyright (c) 2008 Advanced Micro Devices, Inc. All rights reserved.
//
#include "thread/monitor.hpp"
#include "thread/atomic.hpp"
#include "thread/semaphore.hpp"
#include "thread/thread.hpp"
#include "utils/util.hpp"
#include <cstring>
#include <tuple>
#include <utility>
namespace amd {
Monitor::Monitor(const char* name, bool recursive) :
contendersList_(NULL), onDeck_(NULL), waitersList_(NULL),
owner_(NULL), recursive_(recursive)
{
const size_t maxNameLen = sizeof(name_);
if (name == NULL) {
const char* unknownName = "@unknown@";
assert(sizeof(unknownName) < maxNameLen && "just checking");
strcpy(name_, unknownName);
}
else {
strncpy(name_, name, maxNameLen - 1);
name_[maxNameLen - 1] = '\0';
}
}
bool
Monitor::trySpinLock()
{
if (tryLock()) {
return true;
}
for (int s = kMaxSpinIter; s > 0; --s) {
// First, be SMT friendly
if (s >= (kMaxSpinIter - kMaxReadSpinIter)) {
Os::spinPause();
}
// and then SMP friendly
else {
Thread::yield();
}
if (!isLocked()) {
return tryLock();
}
}
// We could not acquire the lock in the spin loop.
return false;
}
void
Monitor::finishLock()
{
Thread* thread = Thread::current();
assert(thread != NULL && "cannot lock() from (null)");
if (trySpinLock()) {
return; // We succeeded, we are done.
}
/* The lock is contended. Push the thread's semaphore onto
* the contention list.
*/
Semaphore& sem = thread->lockSemaphore();
sem.reset();
LinkedNode newHead;
newHead.setItem(&sem);
while (true) {
LinkedNode* head; bool isLocked;
// The assumption is that lockWord is locked. Make sure we do not
// continue unless the lock bit is set.
std::tie(head, isLocked) = contendersList_.get();
if (!isLocked) {
if (tryLock()) {
return;
}
continue;
}
// Set the new contention list head if lockWord is unchanged.
newHead.setNext(head);
if (contendersList_.compareAndSet(head, &newHead, kLocked, kLocked)) {
break;
}
// We failed the CAS. yield/pause before trying again.
Thread::yield();
}
int32_t spinCount = 0;
// Go to sleep until we become the on-deck thread.
while (onDeck_.getReference() != &sem) {
// First, be SMT friendly
if (spinCount < kMaxReadSpinIter) {
Os::spinPause();
}
// and then SMP friendly
else if (spinCount < kMaxSpinIter) {
Thread::yield();
}
// now go to sleep
else {
sem.wait();
}
spinCount++;
}
spinCount = 0;
//
// From now-on, we are the on-deck thread. It will stay that way until
// we successfuly acquire the lock.
//
while (true) {
assert(onDeck_.getReference() == &sem && "just checking");
if (tryLock()) {
break;
}
// Somebody beat us to it. Since we are on-deck, we can just go
// back to sleep.
// First, be SMT friendly
if (spinCount < kMaxReadSpinIter) {
Os::spinPause();
}
// and then SMP friendly
else if (spinCount < kMaxSpinIter) {
Thread::yield();
}
// now go to sleep
else {
sem.wait();
}
spinCount++;
}
assert(newHead.next() == NULL && "Should not be linked");
onDeck_ = NULL;
}
void
Monitor::finishUnlock()
{
// If we get here, it means that there might be a thread in the contention
// list waiting to acquire the lock. We need to select a successor and
// place it on-deck.
while (true) {
// Grab the onDeck_ microlock to protect the next loop (make sure only
// one semaphore is removed from the contention list).
//
if (!onDeck_.compareAndSet(NULL, NULL, kUnlocked, kLocked)) {
return; // Somebody else has the microlock, let him select onDeck_
}
LinkedNode* head; bool isLocked;
while (true) {
std::tie(head, isLocked) = contendersList_.get();
if (head == NULL) {
break; // There's nothing else to do.
}
if (isLocked) {
// Somebody could have acquired then released the lock
// and failed to grab the onDeck_ microlock.
head = NULL;
break;
}
if (contendersList_.compareAndSet(
head, head->next(), kUnlocked, kUnlocked)) {
#ifdef ASSERT
head->setNext(NULL);
#endif // ASSERT
break;
}
}
Semaphore* sem = (head != NULL) ? head->item() : NULL;
onDeck_ = sem;
MemoryOrder::fence();
//
// Release the onDeck_ microlock (end of critical region);
if (sem != NULL) {
sem->post();
return;
}
// We do not have an on-deck thread (sem == NULL). Return if
// the contention list is empty or if the lock got acquired again.
std::tie(head, isLocked) = contendersList_.get();
if (isLocked || head == NULL) {
return;
}
}
}
void
Monitor::wait()
{
Thread* thread = Thread::current();
assert(isLocked() && owner_ == thread && "just checking");
// Add the thread's resume semaphore to the list.
Semaphore& suspend = thread->suspendSemaphore();
suspend.reset();
LinkedNode newHead;
newHead.setItem(&suspend);
newHead.setNext(waitersList_);
waitersList_ = &newHead;
// Preserve the lock count (for recursive mutexes)
uint32_t lockCount = lockCount_;
lockCount_ = 1;
// Release the lock and go to sleep.
unlock();
// Go to sleep until we become the on-deck thread.
int32_t spinCount = 0;
while (onDeck_.getReference() != &suspend) {
// First, be SMT friendly
if (spinCount < kMaxReadSpinIter) {
Os::spinPause();
}
// and then SMP friendly
else if (spinCount < kMaxSpinIter) {
Thread::yield();
}
// now go to sleep
else {
suspend.wait();
}
spinCount++;
}
spinCount = 0;
while (true) {
assert(onDeck_.getReference() == &suspend && "just checking");
if (trySpinLock()) {
break;
}
// Somebody beat us to it. Since we are on-deck, we can just go
// back to sleep.
// First, be SMT friendly
if (spinCount < kMaxReadSpinIter) {
Os::spinPause();
}
// and then SMP friendly
else if (spinCount < kMaxSpinIter) {
Thread::yield();
}
// now go to sleep
else {
suspend.wait();
}
spinCount++;
}
// Restore the lock count (for recursive mutexes)
lockCount_ = lockCount;
onDeck_ = NULL;
MemoryOrder::fence();
}
void
Monitor::notify()
{
assert(isLocked() && owner_ == Thread::current() && "just checking");
LinkedNode* waiter = waitersList_;
if (waiter == NULL) {
return;
}
// Dequeue a waiter from the wait list and add it to the contention list.
waitersList_ = waiter->next();
while (true) {
LinkedNode* node = contendersList_.getReference();
waiter->setNext(node);
if (contendersList_.compareAndSet(node, waiter, kLocked, kLocked)) {
break;
}
}
}
void
Monitor::notifyAll()
{
// NOTE: We could CAS the whole list in 1 shot but this is
// not critical code. Optimize this if it becomes hot.
while (waitersList_ != NULL) {
notify();
}
}
} // namespace amd