mthpc supports the following features.
- Thread framework
- Workqueue
- Centralized barrier
- Wait for completion
- Read-Copy Update
- Scoped lock
- Safe pointer
- Taskflow
#include <mthpc/thread.h>
Write your thread initialization and body functions with the following prototype
. And, delcare the thread group with the MTHPC_DECLARE_THREAD_GROUP
macro.
void func(struct mthpc_thread_group *th);
MTHPC_DECLARE_THREAD_GROUP(name, number_of_thread, init_func, body_func, args);
After that, with mutiple thread groups, you can also put it all together with
the macro, MTHPC_DECLARE_THREAD_CLUSTER
.
MTHPC_DECLARE_THREAD_CLUSTER(name, &groupA, &groupB, ...);
Note that all the object declarations are static size, which means they are not runtime-sized.
Run the thread group/cluster with the blocking until all the threads finished.
Before executing the body function, all of the threads will wait until
all the initializations have been done. This kind of synchronization is
cluster-level.
Moreover, to let the object become async (non-blocking, auto-join) you
can use _async_
function.
void mthpc_thread_run(&threads /* group or cluster */);
void mthpc_thread_async_run(&threads /* group or cluster */);
void mthpc_thread_async_wait(&threads /* group or cluster */);
#include <mthpc/workqueue.h>
void work_func(struct mthpc_work *work);
MTHPC_DECLARE_WORK(name, work_func, args);
To queue the work, use the following functions.
MTHPC_INIT_WORK(struct mthpc_work *work, name, work_func, private);
int mthpc_schedule_work_on(int cpu, struct mthpc_work *work);
int mthpc_queue_work(struct mthpc_work *work);
You can also print out the information of the work.
void mthpc_dump_work(struct mthpc_work *work)
#include <mthpc/centralized_barrier.h>
MTHPC_DEFINE_BARRIER(name);
struct mthpc_barrier name = MTHPC_BARRIER_INIT;
Add the barrier to the location you want with the number of the thread will be blocked by this barrier.
void mthpc_barrier_init(struct mthpc_barrier *b);
void mthpc_centralized_barrier(struct mthpc_barrier *b, size_t n);
#include <mthpc/completion.h>
MTHPC_DECLARE_COMPLETION(name, nr);
struct completion name = MTHPC_COMPLETION_INIT(nr);
void mthpc_completion_init(struct mthpc_completion *completion,
unsigned long nr);
void mthpc_complete(struct mthpc_completion *completion);
void mthpc_wait_for_completion(struct mthpc_completion *completion);
#include <mthpc/rcu.h>
#include <mthpc/rculist.h>
Before requiring the RCU read lock you can first initalize the RCU with
void mthpc_rcu_thread_init(void)
.
In sometime, if you make sure that the thread will never use the RCU again,
you can call void mthpc_rcu_thread_exit(void)
to release RCU resource of
that thread.
void mthpc_rcu_read_lock(void);
void mthpc_rcu_read_unlock(void);
mthpc_rcu_replace_pointer(p, new);
mthpc_rcu_dereference(p);
void mthpc_synchronize_rcu(void);
void mthpc_synchronize_rcu_all(void);
#include <mthpc/scoped_lock.h>
/* For lock_type, see the following description. */
void mthpc_scoped_lock(lock_type);
void mthpc_scoped_lock(lock_type, lock_ptr);
Scoped lock support following lock types:
spinlock
rcu
#include <mthpc/safe_ptr.h>
It is an atomic shared_ptr
.
Support the Sparse checking.
/* Create the safe ptr with raw pointer and their dtor function */
MTHPC_DEFINE_SAFE_PTR(name, new_data, dtor);
/* Create the empty safe ptr */
MTHPC_DECLARE_SAFE_PTR(name);
/* Create the safe ptr from the borrow/move opeations */
MTHPC_DECLARE_SAFE_PTR_FROM_BORROW(type, name, struct mthpc_safe_ptr __mthpc_brw *brw_sp);
MTHPC_DECLARE_SAFE_PTR_FROM_MOVE(type, name, struct mthpc_safe_ptr __mthpc_move *move_sp);
/* Store the new pointer to this safe pointer */
void mthpc_safe_ptr_store(struct mthpc_safe_ptr *sp, void *new_data,
void (*dtor)(void *));
/* Load the context of the protected pointer */
void *mthpc_safe_ptr_load(struct mthpc_safe_ptr *sp);
/* cmpxchg the context of the protected pointer */
int mthpc_safe_ptr_cmpxhg(struct mthpc_safe_ptr *sp, void *expected,
void *desired);
/* Debug */
void mthpc_dump_safe_ptr(struct mthpc_safe_ptr *sp);
To borrow the safe data to another function, use the borrow methods.
Every borrowed safe pointer has the __mthpc_brw
attribute. This means
that the user cannot directly dereference the borrowed safe pointer.
/* borrow the safe pointer to other function. */
function(mthpc_borrow_safe_ptr(safe_ptr));
void function(struct mthpc_safe_ptr __mthpc_brw *borrow_ptr)
{
MTHPC_DECLARE_SAFE_PTR_FROM_BORROW(name, borrow_ptr);
...
}
However, if you want to transfer the ownership of safe pointer to other function, use the move methods.
function(mthpc_move_safe_ptr(safe_ptr));
void function(struct mthpc_safe_ptr __mthpc_move *move_ptr)
{
MTHPC_DECLARE_SAFE_PTR_FROM_MOVE(name, move_ptr);
...
}
#include <mthpc/taskflow.h>
Use mthpc_taskflow_create()
to create the task framework. And, use
mthpc_task_create()
to add the new task.
struct mthpc_taskflow *mthpc_taskflow_create(void);
struct mthpc_task *mthpc_task_create(struct mthpc_taskflow *tf,
void (*func)(void *arg), void *arg);
struct mthpc_task *mthpc_sub_task_create(struct mthpc_task *task,
void (*func)(void *arg), void *arg);
Use precede()
and succeed()
functions to decide which task run first.
After that, use await()
to run all the tasks in the framework.
void mthpc_taskflow_precede(task, forward_tasks...);
void mthpc_taskflow_succeed(task, backward_tasks...);
int mthpc_taskflow_await(struct mthpc_taskflow *tf);
#include <mthpc/spinlock.h>
#include <mthpc/util.h>
#include <mthpc/list.h>
#include <mthpc/debug.h>
#include <mthpc/print.h>
#include <mthpc/guards.h>
- hash table
- mlrcu