Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
293 lines (255 sloc) 10.9 KB
//---------------------------------------------------------
// For conditions of distribution and use, see
// https://github.com/preshing/cpp11-on-multicore/blob/master/LICENSE
//---------------------------------------------------------
#ifndef __CPP11OM_DINING_PHILOSOPHERS_H__
#define __CPP11OM_DINING_PHILOSOPHERS_H__
#include <cassert>
#include <mutex>
#include <vector>
#include <array>
#include <algorithm>
#include <utility>
#include <atomic>
#include "sema.h"
#include "bitfield.h"
//---------------------------------------------------------
// DiningPhiloHelpers
//---------------------------------------------------------
namespace DiningPhiloHelpers
{
inline int left(int index, int numPhilos)
{
return (index > 0 ? index : numPhilos) - 1;
}
inline int right(int index, int numPhilos)
{
++index;
return index < numPhilos ? index : 0;
}
}
//---------------------------------------------------------
// DiningPhilosophers
//---------------------------------------------------------
class DiningPhilosophers
{
private:
int m_numPhilos;
// "Box office" using a mutex.
// m_status keeps track of the status of each philosopher (thread).
// 0: Philosopher is thinking
// 1: Philosopher is eating
// 2+: Philosopher is waiting and must not eat before his/her direct neighbors if they have a lower status.
std::mutex m_mutex;
std::vector<int> m_status;
// "Bouncers"
// Can't use std::vector<DefaultSemaphoreType> because DefaultSemaphoreType is not copiable/movable.
std::unique_ptr<DefaultSemaphoreType[]> m_sema;
int left(int index) const { return DiningPhiloHelpers::left(index, m_numPhilos); }
int right(int index) const { return DiningPhiloHelpers::right(index, m_numPhilos); }
int neighbor(int index, int step) const
{
assert(step >= 0 && step < m_numPhilos);
index += step;
if (index >= m_numPhilos)
index -= m_numPhilos;
return index;
}
// We call tryAdjustStatus after a philosopher finishes eating.
// It fans outward (in the direction of step), trying to decrement the status of each neighbor (to target).
bool tryAdjustStatus(int philoIndex, int target, int step)
{
// Should not already have the target status.
assert(m_status[philoIndex] != target);
if (m_status[philoIndex] == target + 1)
{
// Decrementing this status will bring it to target.
// Make sure the next neighbor doesn't prevent it.
int n = neighbor(philoIndex, step);
assert(m_status[n] != target + 1); // No two neighbors should have equal status > 0.
if (m_status[n] != target)
{
// Decrement it.
m_status[philoIndex] = target;
// If neighbor's status is exactly 1 greater, continue visiting.
if (m_status[n] > target)
tryAdjustStatus(n, target + 1, step);
return true;
}
}
return false;
}
public:
DiningPhilosophers(int numPhilos) : m_numPhilos(numPhilos)
{
m_status.resize(numPhilos);
m_sema = std::unique_ptr<DefaultSemaphoreType[]>(new DefaultSemaphoreType[numPhilos]);
}
void beginEating(int philoIndex)
{
int maxNeighborStatus; // Initialized inside lock
{
std::lock_guard<std::mutex> lock(m_mutex);
assert(m_status[philoIndex] == 0); // Must have been thinking
// Establish order relative to direct neighbors.
maxNeighborStatus = std::max(m_status[left(philoIndex)], m_status[right(philoIndex)]);
m_status[philoIndex] = maxNeighborStatus + 1;
// Sanity check.
for (int i = 0; i < m_numPhilos; i++)
assert(m_status[i] >= 0 && m_status[i] <= m_numPhilos);
}
if (maxNeighborStatus > 0)
m_sema[philoIndex].wait(); // Neighbor has priority; must wait
}
void endEating(int philoIndex)
{
int stepFirst = 1;
int firstNeighbor = neighbor(philoIndex, 1);
int secondNeighbor = neighbor(philoIndex, m_numPhilos - 1);
bool firstWillEat; // Initialized inside lock
bool secondWillEat; // Initialized inside lock
{
std::lock_guard<std::mutex> lock(m_mutex);
assert(m_status[philoIndex] == 1); // Must have been eating
m_status[philoIndex] = 0;
// Choose which neighbor to visit first based on priority
if (m_status[firstNeighbor] > m_status[secondNeighbor])
{
std::swap(firstNeighbor, secondNeighbor);
stepFirst = m_numPhilos - stepFirst;
}
// Adjust neighbor statuses.
firstWillEat = tryAdjustStatus(firstNeighbor, 1, stepFirst);
secondWillEat = tryAdjustStatus(secondNeighbor, 1, m_numPhilos - stepFirst);
// Sanity check.
for (int i = 0; i < m_numPhilos; i++)
assert(m_status[i] >= 0 && m_status[i] <= m_numPhilos);
}
if (firstWillEat)
m_sema[firstNeighbor].signal(); // Release waiting neighbor
if (secondWillEat)
m_sema[secondNeighbor].signal(); // Release waiting neighbor
}
};
//---------------------------------------------------------
// LockReducedDiningPhilosophers
// Version of DiningPhilosophers with a lock-free box office.
//---------------------------------------------------------
class LockReducedDiningPhilosophers
{
private:
int m_numPhilos;
// Lock-free "box office".
// AllStatus::philos keeps track of the status of each philosopher (thread).
// 0: Philosopher is thinking
// 1: Philosopher is eating
// 2+: Philosopher is waiting and must not eat before his/her direct neighbors if they have a lower status.
typedef uint32_t IntType;
BEGIN_BITFIELD_TYPE(AllStatus, IntType)
ADD_BITFIELD_ARRAY(philos, 0, 4, 8) // 8 array elements, 4 bits each
END_BITFIELD_TYPE()
std::atomic<IntType> m_allStatus;
// "Bouncers"
// Can't use std::vector<DefaultSemaphoreType> because DefaultSemaphoreType is not copiable/movable.
std::unique_ptr<DefaultSemaphoreType[]> m_sema;
int left(int index) const { return DiningPhiloHelpers::left(index, m_numPhilos); }
int right(int index) const { return DiningPhiloHelpers::right(index, m_numPhilos); }
int neighbor(int index, int step) const
{
assert(step >= 0 && step < m_numPhilos);
index += step;
if (index >= m_numPhilos)
index -= m_numPhilos;
return index;
}
// We call tryAdjustStatus after a philosopher finishes eating.
// It fans outward (in the direction of step), trying to decrement the status of each neighbor (to target).
bool tryAdjustStatus(AllStatus& allStatus, int philoIndex, int target, int step) const
{
// Should not already have the target status.
assert(allStatus.philos[philoIndex] != target);
if (allStatus.philos[philoIndex] == target + 1)
{
// Decrementing this status will bring it to target.
// Make sure the next neighbor doesn't prevent it.
int n = neighbor(philoIndex, step);
assert(allStatus.philos[n] != target + 1); // No two neighbors should have equal status > 0.
if (allStatus.philos[n] != target)
{
// Decrement it.
allStatus.philos[philoIndex] = target;
// If neighbor's status is exactly 1 greater, continue visiting.
if (allStatus.philos[n] > IntType(target))
tryAdjustStatus(allStatus, n, target + 1, step);
return true;
}
}
return false;
}
public:
LockReducedDiningPhilosophers(int numPhilos)
: m_numPhilos(numPhilos)
, m_allStatus(0)
{
assert(IntType(numPhilos) <= AllStatus().philos.maximum());
assert(numPhilos < AllStatus().philos.numItems());
m_sema = std::unique_ptr<DefaultSemaphoreType[]>(new DefaultSemaphoreType[numPhilos]);
}
void beginEating(int philoIndex)
{
int maxNeighborStatus; // Initialized inside CAS loop.
AllStatus oldStatus = m_allStatus.load(std::memory_order_relaxed);
for (;;) // Begin CAS loop
{
assert(oldStatus.philos[philoIndex] == 0); // Must have been thinking
// Establish order relative to direct neighbors.
maxNeighborStatus = std::max(oldStatus.philos[left(philoIndex)], oldStatus.philos[right(philoIndex)]);
AllStatus newStatus(oldStatus);
newStatus.philos[philoIndex] = maxNeighborStatus + 1;
// Sanity check.
for (int i = 0; i < m_numPhilos; i++)
assert(newStatus.philos[i] <= IntType(m_numPhilos));
// CAS until successful. On failure, oldStatus will be updated with the latest value.
if (m_allStatus.compare_exchange_strong(oldStatus, newStatus, std::memory_order_relaxed))
break;
}
if (maxNeighborStatus > 0)
m_sema[philoIndex].wait(); // Neighbor has priority; must wait
}
void endEating(int philoIndex)
{
int stepFirst = 1;
int firstNeighbor = neighbor(philoIndex, 1);
int secondNeighbor = neighbor(philoIndex, m_numPhilos - 1);
bool firstWillEat; // Initialized inside CAS loop.
bool secondWillEat; // Initialized inside CAS loop.
AllStatus oldStatus = m_allStatus.load(std::memory_order_relaxed);
for (;;) // Begin CAS loop
{
assert(oldStatus.philos[philoIndex] == 1); // Must have been eating
AllStatus newStatus(oldStatus);
newStatus.philos[philoIndex] = 0;
// Choose which neighbor to visit first based on priority
if (newStatus.philos[firstNeighbor] > newStatus.philos[secondNeighbor])
{
std::swap(firstNeighbor, secondNeighbor);
stepFirst = m_numPhilos - stepFirst;
}
// Adjust neighbor statuses.
firstWillEat = tryAdjustStatus(newStatus, firstNeighbor, 1, stepFirst);
secondWillEat = tryAdjustStatus(newStatus, secondNeighbor, 1, m_numPhilos - stepFirst);
// Sanity check.
for (int i = 0; i < m_numPhilos; i++)
assert(newStatus.philos[i] <= IntType(m_numPhilos));
// CAS until successful. On failure, oldStatus will be updated with the latest value.
if (m_allStatus.compare_exchange_strong(oldStatus, newStatus, std::memory_order_relaxed))
break;
}
if (firstWillEat)
m_sema[firstNeighbor].signal(); // Release waiting neighbor
if (secondWillEat)
m_sema[secondNeighbor].signal(); // Release waiting neighbor
}
};
typedef LockReducedDiningPhilosophers DefaultDiningPhilosophersType;
#endif // __CPP11OM_DINING_PHILOSOPHERS_H__