mirror of
https://github.com/Zygo/bees.git
synced 2025-05-17 13:25:45 +02:00
Quite often we want to execute task B after task A finishes executing, especially if tasks A and B attempt to acquire locks on the same objects. Implement that capability in Task directly: each Task holds a queue of Tasks which will be executed strictly after this Task has finished executing, or if the Task is destroyed. Add a local queue to each TaskConsumer. This queue contains a list of Tasks which are to be executed by a single thread in sequential order. These tasks are executed before fetching any tasks from TaskMaster. Each time a Task finishes executing, the list of tasks appended to the recently executed Task are spliced at the beginning of the thread's TaskConsumer local queue. These tasks will be executed in the same thread in the same order they were appended to the recently executed Task. If a Task is destroyed with a post-execution queue, that queue is also inserted at the front of the current TaskConsumer's local queue. If a Task is destroyed or somehow executed outside of a TaskConsumer thread, or a TaskConsumer thread is destroyed, the local queue of Tasks is wrapped in a "rescue_task" Task, and spliced before the head of the global queue. This preserves the sequential ordering of tasks. In all cases the order of sequential execution of Tasks that are appended to another Task is preserved. The unused queue insertion functions are removed. Exclusion is now simply a mutex, a bool, and a Task with an empty function. Tasks that queue up waiting for the mutex are stored in Exclusion's Task, and Exclusion simply runs that task when the ExclusionState is released. Signed-off-by: Zygo Blaxell <bees@furryterror.org>
177 lines
4.7 KiB
C++
177 lines
4.7 KiB
C++
#ifndef CRUCIBLE_TASK_H
|
|
#define CRUCIBLE_TASK_H
|
|
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <ostream>
|
|
#include <string>
|
|
|
|
namespace crucible {
|
|
using namespace std;
|
|
|
|
class TaskState;
|
|
|
|
using TaskId = uint64_t;
|
|
|
|
/// A unit of work to be scheduled by TaskMaster.
|
|
class Task {
|
|
shared_ptr<TaskState> m_task_state;
|
|
|
|
Task(shared_ptr<TaskState> pts);
|
|
|
|
public:
|
|
|
|
/// Create empty Task object.
|
|
Task() = default;
|
|
|
|
/// Create Task object containing closure and description.
|
|
Task(string title, function<void()> exec_fn);
|
|
|
|
/// Schedule Task for at least one future execution.
|
|
/// May run Task in current thread or in other thread.
|
|
/// May run Task before or after returning.
|
|
///
|
|
/// Only one instance of a Task may execute at a time.
|
|
/// If a Task is already scheduled, run() does nothing.
|
|
/// If a Task is already running, run() reschedules the
|
|
/// task after the currently running instance returns.
|
|
void run() const;
|
|
|
|
/// Schedule Task to run after this task has run at least once.
|
|
void append(const Task &task) const;
|
|
|
|
/// Describe Task as text.
|
|
string title() const;
|
|
|
|
/// Returns currently executing task if called from exec_fn.
|
|
/// Usually used to reschedule the currently executing Task.
|
|
static Task current_task();
|
|
|
|
/// Ordering operator for containers
|
|
bool operator<(const Task &that) const;
|
|
|
|
/// Null test
|
|
operator bool() const;
|
|
|
|
/// Unique non-repeating(ish) ID for task
|
|
TaskId id() const;
|
|
};
|
|
|
|
ostream &operator<<(ostream &os, const Task &task);
|
|
|
|
class TaskMaster {
|
|
public:
|
|
/// Blocks until the running thread count reaches this number
|
|
static void set_thread_count(size_t threads);
|
|
|
|
/// Sets minimum thread count when load average tracking enabled
|
|
static void set_thread_min_count(size_t min_threads);
|
|
|
|
/// Calls set_thread_count with default
|
|
static void set_thread_count();
|
|
|
|
/// Creates thread to track load average and adjust thread count dynamically
|
|
static void set_loadavg_target(double target);
|
|
|
|
/// Writes the current non-executing Task queue
|
|
static ostream & print_queue(ostream &);
|
|
|
|
/// Writes the current executing Task for each worker
|
|
static ostream & print_workers(ostream &);
|
|
|
|
/// Gets the current number of queued Tasks
|
|
static size_t get_queue_count();
|
|
|
|
/// Gets the current number of active workers
|
|
static size_t get_thread_count();
|
|
|
|
/// Drop the current queue and discard new Tasks without
|
|
/// running them. Currently executing tasks are not
|
|
/// affected (use set_thread_count(0) to wait for those
|
|
/// to complete).
|
|
static void cancel();
|
|
};
|
|
|
|
// Barrier executes waiting Tasks once the last BarrierLock
|
|
// is released. Multiple unique Tasks may be scheduled while
|
|
// BarrierLocks exist and all will be run() at once upon
|
|
// release. If no BarrierLocks exist, Tasks are executed
|
|
// immediately upon insertion.
|
|
|
|
class BarrierState;
|
|
|
|
class BarrierLock {
|
|
shared_ptr<BarrierState> m_barrier_state;
|
|
BarrierLock(shared_ptr<BarrierState> pbs);
|
|
friend class Barrier;
|
|
public:
|
|
// Release this Lock immediately and permanently
|
|
void release();
|
|
};
|
|
|
|
class Barrier {
|
|
shared_ptr<BarrierState> m_barrier_state;
|
|
|
|
Barrier(shared_ptr<BarrierState> pbs);
|
|
public:
|
|
Barrier();
|
|
|
|
// Prevent execution of tasks behind barrier until
|
|
// BarrierLock destructor or release() method is called.
|
|
BarrierLock lock();
|
|
|
|
// Schedule a task for execution when no Locks exist
|
|
void insert_task(Task t);
|
|
};
|
|
|
|
// Exclusion provides exclusive access to a ExclusionLock.
|
|
// One Task will be able to obtain the ExclusionLock; other Tasks
|
|
// may schedule themselves for re-execution after the ExclusionLock
|
|
// is released.
|
|
|
|
class ExclusionState;
|
|
class Exclusion;
|
|
|
|
class ExclusionLock {
|
|
shared_ptr<ExclusionState> m_exclusion_state;
|
|
ExclusionLock(shared_ptr<ExclusionState> pes);
|
|
ExclusionLock() = default;
|
|
friend class Exclusion;
|
|
public:
|
|
// Calls release()
|
|
~ExclusionLock();
|
|
|
|
// Release this Lock immediately and permanently
|
|
void release();
|
|
|
|
// Test for locked state
|
|
operator bool() const;
|
|
};
|
|
|
|
class Exclusion {
|
|
shared_ptr<ExclusionState> m_exclusion_state;
|
|
|
|
Exclusion(shared_ptr<ExclusionState> pes);
|
|
public:
|
|
Exclusion(const string &title);
|
|
|
|
// Attempt to obtain a Lock. If successful, current Task
|
|
// owns the Lock until the ExclusionLock is released
|
|
// (it is the ExclusionLock that owns the lock, so it can
|
|
// be passed to other Tasks or threads, but this is not
|
|
// recommended practice).
|
|
// If not successful, current Task is expected to call
|
|
// insert_task(current_task()), release any ExclusionLock
|
|
// objects it holds, and exit its Task function.
|
|
ExclusionLock try_lock();
|
|
|
|
// Execute Task when Exclusion is unlocked (possibly
|
|
// immediately).
|
|
void insert_task(Task t = Task::current_task());
|
|
};
|
|
|
|
|
|
}
|
|
|
|
#endif // CRUCIBLE_TASK_H
|