All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
Thread Control API

API reference documentation


Creating a Thread
Terminating a Thread
Joining
Thread-Local Data
Thread Synchronization
Thread Destructors

Generally, using single-threaded, event-driven programming (registering callbacks to be called by an event handling loop running in a single thread) is more efficient than using multiple threads. With single-threaded, event driven designs:

  • there's no CPU time spent switching between threads.
  • there's only one copy of thread-specific memory objects, like the procedure call stack.
  • there's no need to use thread synchronization mechanisms, like mutexes, to prevent race conditions between threads.

Sometimes, this style doesn't fit well with a problem being solved, so you're forced to implement workarounds that severely complicate the software design. In these cases, it is far better to take advantage of multi-threading to simplify the design, even if it means that the program uses more memory or more CPU cycles. In some cases, the workarounds required to avoid multi-threading will cost more memory and/or CPU cycles than using multi-threading would.

But you must be careful with multi-threading. Some of the most tenacious, intermittent defects known to humankind have resulted from the misuse of multi-threading. Ensure you know what you are doing.

Creating a Thread

To create a thread, call le_thread_Create().

All threads are named for two reasons:

  1. To make it possible to address them by name.
  2. For diagnostics.

Threads are created in a suspended state. In this state, attributes like scheduling priority and stack size can use the appropriate "Set" functions. All attributes have default values so it is not necessary to set any attributes (other than the name and main function address, which are passed into le_thread_Create() ). When all attributes have been set, the thread can be started by calling le_thread_Start().

Warning
It is assumed that if a thread T1 creates another thread T2 then only thread T1 will set the attributes and start thread T2. No other thread should try to set any attributes of T2 or try to start it.

Terminating a Thread

Threads can terminate themselves by:

Threads can also tell other threads to terminate by "canceling" them; done through a call to le_thread_Cancel().

If a thread terminates itself, and it is "joinable", it can pass a void* value to another thread that "joins" with it. See Joining for more information.

Canceling a thread may not cause the thread to terminate immediately. If it is in the middle of doing something that can't be interrupted, it will not terminate until it is finished. See 'man 7 pthreads' for more information on cancellation and cancellation points.

Joining

Sometimes, you want single execution thread split (fork) into separate threads of parallel execution and later join back together into one thread later. Forking is done by creating and starting a thread. Joining is done by a call to le_thread_Join(). le_thread_Join(T) blocks the calling thread until thread T exits.

For a thread to be joinable, it must have its "joinable" attribute set (using le_thread_SetJoinable()) prior to being started. Normally, when a thread terminates, it disappears. But, a joinable thread doesn't disappear until another thread "joins" with it. This also means that if a thread is joinable, someone must join with it, or its resources will never get cleaned up (until the process terminates).

le_thread_Join() fetches the return/exit value of the thread that it joined with.

Thread-Local Data

Often, you want data specific to a particular thread. A classic example of is the ANSI C variable errno. If one instance of errno was shared by all the threads in the process, then it would essentially become useless in a multi-threaded program because it would be impossible to ensure another thread hadn't killed errno before its value could be read. As a result, POSIX has mandated that errno be a thread-local variable; each thread has its own unique copy of errno.

If a component needs to make use of other thread-local data, it can do so using the pthread functions pthread_key_create(), pthread_getspecific(), pthread_setspecific(), pthread_key_delete(). See the pthread man pages for more details.

Thread Synchronization

Nasty multi-threading defects arise as a result of thread synchronization, or a lack of synchronization. If threads share data, they MUST be synchronized with each other to avoid destroying that data and incorrect thread behaviour.

Warning
This documentation assumes that the reader is familiar with multi-thread synchronization techniques and mechanisms.

The Legato C APIs provide the following thread synchronization mechanisms:

Thread Destructors

When a thread dies, some clean-up action is needed (e.g., a connection needs to be closed or some objects need to be released). If a thread doesn't always terminate the same way (e.g., if it might be canceled by another thread or exit in several places due to error detection code), then a clean-up function (destructor) is probably needed.

Legato threads use le_thread_AddDestructor() for clean-up functions. It registers a function to be called by a specified thread just before it terminates. A parent thread can also call le_thread_AddChildDestructor() to register a destructor for a child thread before it starts the child thread.

Multiple destructors can be registered for the same thread. They will be called in reverse order of registration (i.e, the last destructor to be registered will be called first).


Copyright (C) Sierra Wireless, Inc. 2014. All rights reserved. Use of this work is subject to license.