Threads
This page has been automatically translated using the Google Translate API services. We are working on improving texts. Thank you for your understanding and patience.
The threads are parts of the same program that can run in parallel.
Functions
Thread* | bthread_create (...) |
int | bthread_current_id (void) |
void | bthread_close (...) |
void | bthread_cancel (...) |
uint32_t | bthread_wait (...) |
bool_t | bthread_finish (...) |
void | bthread_sleep (...) |
The threads are different execution paths within the same process (Figure 1). They are also known as light processes, since they are more agile to create and manage than the processes themselves. They share code and memory space with the main program, so it is very easy to exchange information between them through memory variables. A thread starts its execution in a method known as thread_main and, at the moment it is launched, it runs in parallel with the main thread. Like the processes, they are objects controlled by the core of the system that will dictate, ultimately, whether the threads will be executed in another CPU core (true multitasking) or will share it (context switch).
- Use bthread_create to create a new thread.
- Use bthread_wait to force the main thread to wait for the thread to execute.
1. Throwing threads
Each call to bthread_create will create a new thread in parallel starting at the function passed as a parameter (thread_main). The "natural" way to end it is by returning from thread_main, although it is possible to abort it from the main thread.
1 2 3 4 5 6 7 8 9 10 11 |
static uint32_t i_thread(ThData *data) { // Do something ... // Thread execution ends return 0; } Thread *thread = bthread_create(i_thread, data, ThData); // Main thread will continue here // Second thread will run 'i_thread' |
2. Shared variables
Each new thread has its own Stack Segment therefore, all automatic variables, function calls and dynamic allocations will be private to said thread. But it can also receive global data from the process through the thread_main data
parameter. We must be careful when accessing global data through multiple concurrent threads, since modifications made by other threads can alter the logical code execution, producing errors that are very difficult to debug. The program (Listing 1) is correct for single-thread programs, but if the variable vector
is accessed by two simultaneous threads, can lead to a Segmentatin Fault error if thread-1 frees memory while thread-2 is executing the loop.
1 2 3 4 5 6 7 8 |
To avoid this problem, we will have to protect the access to shared variables through a Mutex (Listing 2). This Mutual exclusion mechanism guarantees that only one thread can access the resource in a moment of time. A thread will be stopped if it intends to execute the code located between bmutex_lock and bmutex_unlock if another thread is within this critical section.
1 2 3 4 5 6 7 8 9 10 |
bmutex_lock(shared->mutex); if (shared->vector != NULL) { shared->total = 0; for(i = 0; i < shared->n; i++) shared->total += shared->vector[i]; bmem_free(shared->vector); shared->vector = NULL; } bmutex_unlock(shared->mutex); |
3. Multi-thread example
The tricky part of multi-threaded programming is to decompose a solution into parts that can run in parallel and organize the data structures so that this can be carried out in the most balanced way possible. In (Listing 3) the program will run four times faster (x4) since a perfect division of the problem has been made (Figure 2). This is just a theoretical example and this result will be very difficult to achieve in real situations. We must also minimize the number of shared variables and the time of the critical sections, otherwise the possible inter-blocks will reduce the gain.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
typedef struct _app_t App; typedef struct _thdata_t ThData; struct _app_t { uint32_t total; uint32_t n; uint32_t *elems; Mutex *mutex; }; struct _thdata_t { uint32_t thread_id; uint32_t start; uint32_t end; uint64_t time; App *app; }; static uint32_t i_thead(ThData *data) { uint32_t i, total = 0; uint64_t t1 = btime_now(); for (i = data->start; i < data->end; ++i) { // Simulates processing uint32_t time = bmath_randi(0, 100); bthread_sleep(time); total += data->app->elems[i]; } // Mutual exclusion access to shared variable 'total' bmutex_lock(data->app->mutex); data->app->total += total; bmutex_unlock(data->app->mutex); data->time = (btime_now() - t1) / 1000; return data->thread_id; } // Threads creating function uint32_t i, m; uint64_t t; App app; ThData thdata[4]; Thread *thread[4]; // App data vector i_init_data(&app); app.mutex = bmutex_create(); m = app.n / 4; // Thread data for (i = 0; i < 4; ++i) { thdata[i].thread_id = i; thdata[i].app = &app; thdata[i].start = i * m; thdata[i].end = (i + 1) * m; } // Launching threads t = btime_now(); for (i = 0; i < 4; ++i) thread[i] = bthread_create(i_thead, &thdata[i], ThData); // Wait for threads end for (i = 0; i < 4; ++i) { uint32_t thid = bthread_wait(thread[i]); bstd_printf("Thread %d finished in %d ms.\n", thid, thdata[thid].time); bthread_close(&thread[i]); } // Process total time t = (btime_now() - t) / 1000; bstd_printf("Proccessing result = %d in %d ms.\n", app.total, t); bmutex_close(&app.mutex); |
1 2 3 4 5 |
Thread 0 finished in 13339 ms. Thread 1 finished in 12506 ms. Thread 2 finished in 12521 ms. Thread 3 finished in 12999 ms. Proccessing result = 499500 in 13344 ms. |
bthread_create ()
Create a new execution thread, which starts in thmain
.
Thread* bthread_create(FPtr_thread_main thmain, type *data, type);
thmain | The thread start function thread_main. Shared data can be passed through the data pointer. |
data | Data passed as a parameter to |
type | Type of |
Return
Thread handle. If the function fails, return NULL
.
Remarks
The thread will run in parallel until thmain
return or call bthread_cancel. Throwing threads.
bthread_current_id ()
Returns the identifier of the current thread, that is, the one that is running when this function is called.
int bthread_current_id(void);
Return
Thread identifier.
bthread_close ()
Close the thread handler and free resources.
void bthread_close(Thread **thread);
thread | Thread handle. It will be put to |
Remarks
If the thread is still running, this function does not finish it. Like any other object, a thread must always be closed, even if it has already finished its execution. Throwing threads.
bthread_cancel ()
Force a thread termination.
void bthread_cancel(Thread *thread);
thread | Thread handler. |
Remarks
It is not recommended to call this function. There will be no "clean" exit of the thread. If it is within a critical section, it will not be released. Neither will it release the dynamic memory reserved privately by the thread. The correct way to end a thread of execution is to return thmain
. Shared variables can be used (Mutual exclusion) to indicate to a thread that it should end cleanly.
bthread_wait ()
Stops the thread that calls this function until thread
finishes its execution.
uint32_t bthread_wait(Thread *thread);
thread | Thread handle to which we must wait. |
Return
The thread return value. If an error occurs, return UINT32_MAX.
bthread_finish ()
Check if the thread is still running.
bool_t bthread_finish(Thread *thread, uint32_t *code);
thread | Thread handler. |
code | The return value of the thmain function (if it has ended). Can be |
Return
TRUE
if the thread has finished, FALSE
otherwise.
Remarks
This function returns immediately.
bthread_sleep ()
Suspends the execution of the current thread (the one that calls this function) for a certain number of milliseconds.
void bthread_sleep(const uint32_t milliseconds);
milliseconds | Time interval (in milliseconds) that the suspension will last. |
Remarks
Performs a "passive" suspension, where no "empty loop" will be executed. The thread is dropped by the scheduler and reactivated later.