Cross-platform C SDK logo

Cross-platform C SDK

Threads

❮ Back
Next ❯
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 (...)
intbthread_current_id (void)
voidbthread_close (...)
voidbthread_cancel (...)
uint32_tbthread_wait (...)
bool_tbthread_finish (...)
voidbthread_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.
  • Schematic of a process with multiple execution threads.
    Figure 1: A process with multiple execution threads.

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.

Basic code to launch a parallel execution 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.

Listing 1: Dangerous access to shared variables.
1
2
3
4
5
6
7
8
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;
}

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.

Listing 2: Secure access to shared variables.
 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.

Schematic of several threads collaborating in the calculation of a vector.
Figure 2: Collaboration of four threads in a vector calculation.
Listing 3: Multi-threaded processing of a very large vector.
 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);
Listing 4: Resultado.
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 thmain.

type

Type of data.

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 NULL after closing.

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 NULL.

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.

❮ Back
Next ❯