LAR Library: Multi-threading

LAR Library

Multi-threading

Macros

#define TASK_INFINITE_TIMEOUT   (0xFFFFFFFFLU)
 Use this constant to wait forever when a timeout is necessary. More...
 

Typedefs

typedef struct taskThread_t taskThread_t
 A handle to a running thread.
 
typedef void(* taskThreadFunction_t) (void *param)
 Type of a function pointer to be used as entry point of a new thread. More...
 
typedef struct taskQueue_t taskQueue_t
 Handle to a multi-threading queue.
 
typedef struct taskSemaphore_t taskSemaphore_t
 Handle to a semaphore.
 
typedef struct taskEvent_t taskEvent_t
 Handle to an event.
 

Functions

taskThread_ttaskThreadCreate (taskThreadFunction_t fn, void *param, uint32_t stackSize)
 Create a new execution thread with entry point fn. More...
 
void taskSleep (uint32_t timeout)
 Sleep the current running thread. More...
 
taskThread_ttaskThreadCurrent (void)
 Return the handle of the current running thread. More...
 
taskQueue_ttaskQueueCreate (int maxItems)
 Create a new queue with up to maxItems allowed. More...
 
void taskQueueDestroy (taskQueue_t *q)
 Destroy a queue and discards all pending elements. More...
 
int taskQueuePost (taskQueue_t *q, void *item)
 Insert an element on the back of the queue q. More...
 
int taskQueuePop (taskQueue_t *q, void **item, uint32_t timeout)
 Extract the element from the front of the queue. More...
 
taskSemaphore_ttaskSemaphoreCreate (int limit)
 Create a semaphore with a maximum entry count of limit. More...
 
void taskSemaphoreDestroy (taskSemaphore_t *s)
 Destroy an existing semaphore. More...
 
int taskSemaphoreAcquire (taskSemaphore_t *s, uint32_t timeout)
 Acquire (also called wait or down) the semaphore. More...
 
int taskSemaphoreRelease (taskSemaphore_t *s)
 Release (also called signal or up) a previously acquired semaphore. More...
 
taskEvent_ttaskEventCreate (void)
 Create a new event handler. More...
 
taskEvent_ttaskEventCreateSystem (uint32_t bitmask)
 Creates an event handler to wait for system peripheral events. More...
 
void taskEventDestroy (taskEvent_t *e)
 Release the resources associated with an event handler. More...
 
int taskEventSignal (taskEvent_t *e)
 Set an event to the signaled state. More...
 
int taskEventCheck (taskEvent_t *e)
 Check if an event if signaled, without waiting or clearing the signal. More...
 
int taskEventWait (taskEvent_t *e, uint32_t timeout)
 Wait for e to change to signaled state. More...
 
int taskEventWaitAny (taskEvent_t *events[], int nevents, uint32_t timeout)
 Wait for at least one among a list of events to be signaled. More...
 
int taskEventClear (taskEvent_t *e)
 Set an event as non-signaled. More...
 

Detailed Description

Rationale

Multi-threading capabilities are necessary for cases such as background dialing, controlling of external devices and background processing. Unfortunately OS capabilities and APIs vary widely, so a common ground is necessary to ease porting.

Introduction

This module deals with multi-threading, or the creation of simultaneous threads of execution, and synchronization of those threads. The following objects are given:

  • Threads: an application may create parallel threads of execution;
  • Queues: FIFO (first-in-first-out) sequences of values designed so one thread can communicate with other in an orderly manner;
  • Events: signals between threads, that carry no other information other than the event has been triggered;
  • Semaphores: restrict simultaneous access to resources.

Threads

Each thread is a parallel point of execution that has access to the same global environment (global variables, APIs) as the application as a whole, but a local stack (for local variables).

Because all threads access the same set of global variables great care must be taken so two threads do not attempt to modify the same memory location at the same time.

Each thread has an associated amount of memory for its stack, the application may "suggest" a minimum size for this stack on the call to taskThreadCreate(), but the size of the stack is ultimately platform-dependent.

After a thread has finished running (i.e. the user provided function has returned) its taskThread_t handle becomes invalid, it is the user responsibility to guarantee that the handle is not used in this case.

This module does not provide any form of thread-local storage, if such is required the application must implement this itself.

Attention
Each platform may have an unspecified limit on the number of concurrent tasks that may be created at each time!

See http://en.wikipedia.org/wiki/Thread_(computer_science) and http://en.wikipedia.org/wiki/Thread_safety

Queues

A queue is a variable-sized array of elements with first-in-first-out semantics. The two basic operations provided are taskQueuePost() which adds an element to the front of the queue, and taskQueuePop() which removes an element from the back of the queue.

This implementation is limited to queue elements of type void*, they can be used to directly encode integer values (small enough to fit on an address) or point themselves to the actual value.

Queues are the recommended method of intra-thread communication, but the application must be careful to enforce move semantics: i.e. when a value (specially in case of pointers) is sent from one thread to another using a queue, the sender thread is in effect relinquishing ownership of this element and passing to the receiver thread. Carefully applying this methodology has the potential of reducing errors caused by simultaneous access to memory locations (see reference below).

Note that, as in the Double-ended queue module, the queue does no special handling of the void* pointers stored in it, more specifically, it will not call memFree() on any pointer, the application must be careful to release any memory itself (specially when taskQueueDestroy() is called).

See http://en.wikipedia.org/wiki/Message_passing

Events

Events signal the occurrence of something. They are a binary marker with the states signaled and non-signaled. When created, a taskEvent_t is in non-signaled state, and only a call to taskEventSignal() will change this.

Threads may wait until an event is in signaled state by calling taskEventWait() or taskEventWaitAny(). The difference is that the first is a simpler API when it is necessary to wait for only one event handle, while the second allows to wait for multiple event handles on the same call.

Device Events

Both supported platforms (Unicapt32, Telium) use a bitmap (encoded in a 32-bit unsigned integer) to mark which events have occurred. This API support interface with those OS-provided event facilities by using taskEventCreateSystem().

A taskEvent_t created this way works as any other event when used with taskEventWait() or taskEventWaitAny(), but will not work when used with taskEventSignal(), only OS APIs will work.

Note that since which bits are used for which peripherals changes from platform to platform, this in itself is not a completely portable solution.

Note
On Unicapt32 only device events are supported, user events generated by psyEventSend() and such are not supported, this is because the bitmap associated with each event defines the first parameter to psyPeripheralResultWait(), but do not handle the case of PSY_EVENT_RECEIVED.

Semaphores

Semaphores limit the number of threads that can simultaneously enter a given region of code, called a critical-region. The most common case being of allowing only one thread to enter a critical region at a time.

This is useful to control access to resources where a queue or an event would not apply, for example access to a file.

For example, consider the (very) constrained case where two threads are repeatedly appending information to a same file using a function appendToFile() that receives as parameter the name of the file and a string message to append to it:

static void init() {
mutex = taskSemaphoreCreate(1); // only one thread at a time
}
static void thread1(void *p) {
for (;;) {
appendToFile("global.txt", "from thread 1");
}
}
static void thread2(void *p) {
for (;;) {
appendToFile("global.txt", "from thread 2");
}
}
void test(void) {
init();
taskThreadCreate(&thread1, NULL, 0);
taskThreadCreate(&thread2, NULL, 0);
taskThreadSleep(TASK_INFINITE_TIMEOUT);
}

The semaphore mutex is used to forbid the two tasks from access the file "global.txt" at the same time. (Note that this convoluted example is un-optimal in many respects. If you find yourself writing this sort of architecture on a real application, please reconsider! A more scalable approach would be having an append-to-file thread that receives requests of strings to write to the global file from a taskQueue_t).

See http://en.wikipedia.org/wiki/Semaphore_(programming)

Macro Definition Documentation

#define TASK_INFINITE_TIMEOUT   (0xFFFFFFFFLU)

Use this constant to wait forever when a timeout is necessary.

Typedef Documentation

typedef void(* taskThreadFunction_t) (void *param)

Type of a function pointer to be used as entry point of a new thread.

Parameters
paramThe user-provided param as given to taskThreadCreate()

Function Documentation

int taskEventCheck ( taskEvent_t e)

Check if an event if signaled, without waiting or clearing the signal.

Attention
This does not work with events created by taskEventCreateSystem().
Parameters
eHandle to event
Returns
BASE_ERR_OK if e is signaled.
BASE_ERR_TIMEOUT if e was not signaled.
BASE_ERR_INVALID_HANDLE if e is invalid ir was created by taskEventCreateSystem().
BASE_ERR_RESOURCE_PB in case of internal error.
int taskEventClear ( taskEvent_t e)

Set an event as non-signaled.

This call may be used to "clean the slate" before other operations, discarding previous signals.

Attention
This does not work with events created by taskEventCreateSystem().
Parameters
eHandle to event.
Returns
BASE_ERR_OK on success.
BASE_ERR_INVALID_HANDLE if e is invalid.
BASE_ERR_RESOURCE_PB in case of internal error
taskEvent_t* taskEventCreate ( void  )

Create a new event handler.

The event is created in non-signaled state. Multiple events can be created and the limit on number of created events is not related to the platform event handling primitives.

Returns
An event handler or NULL on error.
taskEvent_t* taskEventCreateSystem ( uint32_t  bitmask)

Creates an event handler to wait for system peripheral events.

Both Unicapt32 and Telium platforms use a bitmap to check for device events (see psyPeripheralResultWait() and ttestall()). This call provides a bridge between taskEvent_t and system peripheral events.

Internally any call to taskEventWait() or taskEventWaitAny() with events created this way will call the OS functionality to wait for peripherals.

For platforms where this form of event handling does not exist, this call will return NULL.

Event handlers created in this way also need to be destroyed by calling taskEventDestroy().

Note
Signaling an event created this way (by taskEventSignal()) is not supported. Those events can only be triggered by the OS, either internally or by calling OS APIs.
Parameters
bitmaskWhich system event bits to wait for. The 31st bit (0x80000000) is reserved for internal use, trying to create an event for it will return NULL instead.
Returns
A valid taskEvent_t handle, or NULL if bitmask is invalid or peripheral events are not supported in this platform.
void taskEventDestroy ( taskEvent_t e)

Release the resources associated with an event handler.

What happens to threads blocked waiting for this event is undefined and platform-dependent.

Parameters
eHandle to event.
int taskEventSignal ( taskEvent_t e)

Set an event to the signaled state.

If the event is already signaled, nothing changes.

Attention
This does not work with events created by taskEventCreateSystem().
Parameters
eHandle to event.
Returns
BASE_ERR_OK on success.
BASE_ERR_INVALID_HANDLE if e is invalid or created with taskEventCreateSystem().
int taskEventWait ( taskEvent_t e,
uint32_t  timeout 
)

Wait for e to change to signaled state.

If e is already signaled, will return immediately.

Non-system events are automatically changed back to non-signaled state. For system events this is platform-dependent.

Parameters
eHandle to event.
timeoutMax time to wait for e to signal, in hundreds of second.
Returns
BASE_ERR_OK if e was signaled.
BASE_ERR_TIMEOUT if e was not signaled.
BASE_ERR_INVALID_HANDLE if e is invalid.
BASE_ERR_RESOURCE_PB in case of internal error
int taskEventWaitAny ( taskEvent_t events[],
int  nevents,
uint32_t  timeout 
)

Wait for at least one among a list of events to be signaled.

Calling this function with nevents == 1 is equivalent to a call to taskEventWait().

Example:

1 // ... events e1 and e2 created somewhere else ...
2 taskEvent_t *events[] = { e1, e2 };
3 int which = taskEventWaitAny(events, 2, TASK_INFINITE_TIMEOUT);
4 if (which == 0) event_e1_happened();
5 else if (which == 1) event_e2_happened();
6 else error_happened();
Parameters
eventsArray of event handles to wait for.
neventsNumber of elements in events.
timeoutMax time to wait for an event to signal, in hundreds of second.
Returns
The (zero-based) index (inside events) of the first event to be detected.
BASE_ERR_TIMEOUT if no event was signaled.
BASE_ERR_INVALID_HANDLE if any element in events is invalid.
BASE_ERR_RESOURCE_PB in case of internal error
taskQueue_t* taskQueueCreate ( int  maxItems)

Create a new queue with up to maxItems allowed.

Parameters
maxItemsMax number of items that fit on this queue.
Returns
Handle to new queue or NULL on error.
void taskQueueDestroy ( taskQueue_t q)

Destroy a queue and discards all pending elements.

Parameters
qQueue to be discarded.
Note
The queue elements are discarded with no further processing, which may cause resource and/or memory leaks. If a taskQueuePop() is waiting the behavior is undefined and platform-dependent.
int taskQueuePop ( taskQueue_t q,
void **  item,
uint32_t  timeout 
)

Extract the element from the front of the queue.

Example:

1 taskQueue_t *q = get_queue_handle();
2 void *value;
3 if (taskQueuePop(q, &value, TASK_INFINITE_TIMEOUT) == BASE_ERR_OK) {
4  process((myType_t *) value);
5 }
Parameters
qHandle to queue.
[out]itemWhere to store the element extracted from q (note the double indirection!).
timeoutTime to wait for element, in hundreds of seconds.
Returns
BASE_ERR_OK on success
BASE_ERR_INVALID_HANDLE if q is invalid
BASE_ERR_INVALID_PARAMETER if item is NULL
BASE_ERR_TIMEOUT if no element was received in timeout
int taskQueuePost ( taskQueue_t q,
void *  item 
)

Insert an element on the back of the queue q.

Example:

1 taskQueue_t *q = taskQueueCreate(NITEMS);
2 taskQueuePost(q, (void *) 0x12345678); // hand-crafted pointer value
3 taskQueuePost(q, (void *) &someVariable); // pointer to existing variable
Parameters
qHandle to queue.
itemItem to be inserted.
Returns
BASE_ERR_OK on success
BASE_ERR_INVALID_HANDLE if q is invalid.
BASE_ERR_OVERFLOW if q already has the max number of elements allowed.
int taskSemaphoreAcquire ( taskSemaphore_t s,
uint32_t  timeout 
)

Acquire (also called wait or down) the semaphore.

May block if the s access count is full, waiting for a slot to vacant.

Parameters
sHandle to semaphore
timeoutMax time to wait for a vacancy, in hundreds of seconds.
Returns
BASE_ERR_OK on success (semaphore was acquired)
BASE_ERR_TIMEOUT if could no acquire semaphore in timeout
BASE_ERR_INVALID_HANDLE if s is invalid
taskSemaphore_t* taskSemaphoreCreate ( int  limit)

Create a semaphore with a maximum entry count of limit.

Parameters
limitNumber of threads that may simultaneously acquire the returned semaphore.
Returns
Handle to semaphore or NULL on error.
void taskSemaphoreDestroy ( taskSemaphore_t s)

Destroy an existing semaphore.

The effects of destroying a semaphore that is being waited on is undefined and platform-dependent.

Parameters
sHandle to semaphore.
int taskSemaphoreRelease ( taskSemaphore_t s)

Release (also called signal or up) a previously acquired semaphore.

Parameters
sHandle to semaphore.
Returns
BASE_ERR_OK on success
BASE_ERR_INVALID_HANDLE if s is invalid
void taskSleep ( uint32_t  timeout)

Sleep the current running thread.

This will cause the current thread to stop executing for timeout hundreds of second, but other threads will keep running.

Parameters
timeoutTime to sleep the current thread, in hundreds of seconds.
taskThread_t* taskThreadCreate ( taskThreadFunction_t  fn,
void *  param,
uint32_t  stackSize 
)

Create a new execution thread with entry point fn.

Parameters
fnEntry point for thread execution.
paramParameter to be passed to fn.
stackSizeA hint on the minimum stack size required for this thread. Depending on the platform, different limitations on minimum and maximum stack size will apply. Use 0 for a default value that is at least 2KiB in all supported platforms.
Returns
Handle to newly created thread, or NULL on error.
taskThread_t* taskThreadCurrent ( void  )

Return the handle of the current running thread.

Returns
Handle to current thread.
Generated on Mon Mar 27 2017 15:42:53 for LAR Library by   doxygen 1.8.9.1