The Sofia utility library provides simple OS-independent synchronization interface. The synchronization interface contains primitives for managing events, messages, timers and threads.
|Example and test code for syncronization and threads|
|#define||SU_INTERNAL_P su_root_t *|
|int||su_root_size_hint = 64|
Root object gives access to the task control. The root object represents the task to the code running within task. Through the root, the task code can access its context object (magic) and thread-synchronization features like wait objects, timers, and messages.
When a message is sent between tasks, a task reference su_task_r is used to reprent the task address. Reference counting is used to make sure that the task references stay valid.
The public API contains following functions:
New tasks can be created via su_clone_start() function.su_root_t object using the su_root_register() function. Whenever the wait object receives an event, the registered callback function is invoked.
When successful, the su_root_register() returns an small non-negative integer representing the registration. The registration can be manipulated with su_root_eventmask() function, for instance, when sending through a socket block, the application can add SU_WAIT_OUT event to the mask.
The registration can be removed using su_root_deregister() function.
The clones are useful for handling tasks that can be executed by a separate threads, but which do not block excessively. When threads are not available or they are not needed, clones can also be run in a single-threaded mode. Running in single-threaded mode is especially useful while debugging.
A clone task is created with function su_clone_start(). Each clone has its own root object (su_root_t), which holds a context pointer (su_root_magic_t *). The context object can be different from that of parent task.
When a clone is started, the clone initialization function is called. The initialization function should do whatever initialization there is to be performed, register I/O events and timers, and then return. If the initialization is successful, the clone task reverts to run the event loop and invoking the event callbacks until its parent stops it by calling su_clone_wait() which invokes the deinit function. The clone task is destroyed when the deinit function returns.
The public API consists of following functions:
Timer interface for su_root.
Timers are used to schedule some task to be executed at given time or after a default interval. The default interval is specified when the timer is created. We call timer activation "setting the timer", and deactivation "resetting the timer" (as in SDL). When the given time has arrived or the default interval has elapsed, the timer expires and it is ready for execution.
The functions used to create, destroy, activate, and manage timers are as follows:
Usually, timer wakeup function should be called at regular intervals. In such case, the timer is activated using function su_timer_set_for_ever(). When the timer is activated it is given the wakeup function and pointer to context data:
su_timer_set_for_ever(timer, timer_wakeup, args);
When the interval has passed, the root event loop calls the wakeup function:
timer_wakeup(root, timer, args);
If the number of calls to callback function is important, use su_timer_run() instead. The run timer tries to compensate for missed time and invokes the callback function several times if needed. (Because the real-time clock can be adjusted or the program suspended, e.g., while debugged, the callback function can be called thousends of times in a row.) Note that while the timer tries to compensate for delays occurred before and during the callback, it cannot be used as an exact source of timing information.
Timer ceases running when su_timer_reset() is called.
Alternatively, the timer can be set for one-time event invocation. When the timer is set, it is given the wakeup function and pointer to context data. The actual duration can also be specified using su_timer_set_at().
su_timer_set(timer, timer_wakeup, args);
When the timer expires, the root event loop calls the wakeup function:
timer_wakeup(root, timer, args);
If the timed event is not needed anymore, the timer can be reset:
If the timer is expected to be called at regular intervals, it is possible to set ro run continously with su_timer_run(). While such a continously running timer is active it must not be set using su_timer_set() or su_timer_set_at().
When the timer is not needed anymore, the timer object itself should be destroyed:
It is possible to combine several events with |, binary or operator.
The wait objects can be managed with functions as follows:
poll. The structure contains a file descriptor, a mask describing expected events, and a mask containing the occurred events after calling
su_wait(), ie. poll().
In Windows, the wait object is a
HANDLE (a descriptor of a Windows kernel entity).