📜 ⬆️ ⬇️

Cross-platform multithreaded applications

To create portable multi-threaded applications, I suggest using the Glib library.
Glib is a fairly large cross-platform library, which, in addition to threads, includes support for internationalization, working with strings, arrays, files, timers, and much more, including XML parser and support for .ini configuration files.
At the same time, this library is rather small and has almost no dependencies, which makes it possible to include it in Windows projects without any problems, and in unix-like systems, Glib already exists.

This option is primarily suitable for cross-platform console and graphical GTK applications.

Thread programming


To use threads in an application, you first need to perform the initialization function:
g_thread_init (NULL);

Creating a new thread


GThread* g_thread_create (GThreadFunc func, gpointer data, gboolean joinable, GError **error);
Description of parameters:
func - streaming function with a prototype of the form: gpointer GThreadFunc (gpointer data);
data - a pointer to additional data passed to the stream, maybe NULL.
joinable - can we expect the end of the thread through g_thread_join ()
error - the code and description of the error when creating the stream, may be NULL.
')

Learn the current thread's gthread


GThread * g_thread_self (void);

Change thread priority:


void g_thread_set_priority (GThread * thread_id, GThreadPriority priority);
Description of parameters:
thread_id - structure obtained when creating a thread via g_thread_create ()
priority - priority, possible options in ascending order:
G_THREAD_PRIORITY_LOW
G_THREAD_PRIORITY_NORMAL
G_THREAD_PRIORITY_HIGH
G_THREAD_PRIORITY_URGENT

Wait for the completion of another thread:


gpointer g_thread_join (GThread * thread_id);
Description of parameters:
thread_id - structure obtained when creating a thread via g_thread_create ()
The function will wait for the completion of the stream, only if during its creation the joinable = TRUE, and returns the completion code for the streaming function,
When creating a stream with joinable = FALSE, the function will immediately finish its execution and return 0.

Example:

// ;
gpointer thread_func(gpointer data)
{
printf(" \n");
return 0;
}

int main (int argc, char *argv[])
{
//
g_thread_init(NULL);
//
GThread *thread_id = g_thread_create(thread_func,NULL,TRUE,NULL);
//
g_thread_join(thread_id);
printf(" \n");
return 0;
}


Thread synchronization


Consider mutexes (mutex) and signals (condition). There are other specific methods, such as blocking bits, but almost all of them can be replaced by mutexes and / or signals.

Mutex


Mutexes are used to protect the code from sharing. This is an analogue of the critical section for Windows.
As soon as one thread has captured a mutex, the other threads, when attempting to capture a mutex, will wait for its release, therefore only one thread has access to the code inside the mutex at any given time.

Creating a mutex:


GMutex * g_mutex_new ();

Mutex lock:


void g_mutex_lock (GMutex * mutex);

Unlocking mutex:


void g_mutex_unlock (GMutex * mutex);

Attempt to lock mutex:


gboolean g_mutex_trylock (GMutex * mutex);
If the mutex is free, it is blocked and the function returns TRUE, otherwise the function terminates its work immediately and returns FALSE.

Removing a mutex:


void g_mutex_free (GMutex * mutex);

An example in which the function critical_func () with a code protected from sharing can be called simultaneously from the main and additional threads:
void critical_func(GMutex *mutex)
{
g_mutex_lock (mutex);
// -
...
g_mutex_unlock (mutex);
}

// ;
gpointer thread_func(gpointer data)
{
GMutex *mutex = (GMutex*)data;
critical_func(mutex);
return 0;
}

int main (int argc, char *argv[])
{
GMutex *mutex = g_mutex_new();
//
GThread *thread_id = g_thread_create(thread_func,mutex,TRUE,NULL);
critical_func(mutex);
//
g_thread_join(thread_id);
return 0;
}


Note: Recalling g_mutex_lock () inside an already locked mutex will not block the program and will simply be ignored, which protects it from re-locking.

Condition


Condition is translated as Condition , but the word Signal is more appropriate for the technique of the case. This is an analogue of the Events for Windows, but not complete: If the signal is sent before it began to wait, then it will go into emptiness and it will be useless to wait for it. Therefore, there is no concept of resetting an event, as in Windows.

Signal creation (conditions):


GCond * g_cond_new (void);

Send a signal:


void g_cond_signal (GCond * cond);

Endlessly wait for a signal:


void g_cond_wait (GCond * cond, GMutex * mutex);

Wait for the signal no more than the specified time:


gboolean g_cond_timed_wait (GCond * cond, GMutex * mutex, GTimeVal * abs_time);

Example

The example demonstrates the use of signals to wait for a thread to start running after it has been created.
GMutex *mutex = NULL;
GCond *cond = NULL;
// ;
gpointer thread_func(gpointer data)
{
//
// ...
//
g_mutex_lock (mutex);
g_cond_signal(cond);
g_mutex_unlock(mutex);//
// ...
return 0;
}

//
int main (int argc, char *argv[])
{
GTimeVal timeval;
gboolean return_val;
//
g_thread_init(NULL);
// ()
mutex = g_mutex_new();
cond = g_cond_new();
// ,
g_mutex_lock(mutex);
//
g_thread_create( thread_func,NULL,TRUE,NULL);
//
g_get_current_time(&timeval);//
g_time_val_add(&timeval,G_USEC_PER_SEC);//
//
return_val = g_cond_timed_wait(cond,mutex,&timeval);// mutex ,
if(!return_val)
printf(" \n");
g_mutex_unlock(mutex);
return 0;
}

Warning: Waiting for a signal must be inside a locked mutex, and sending a signal is also recommended inside a locked mutex, although this is not necessary.
While waiting, the mutex is temporarily unlocked in order to be able to go inside the same mutex from another thread and send a signal.

Where to get Glib


Linux:


Glib is in every Linux distribution, you can check it as follows:
pkg-config --cflags glib-2.0

Windows:


It requires several dll libraries: libgthread-2.0-0.dll, libglib-2.0-0.dll and intl.dll.
For Glib 2.28.8, these files take up 1.4 MB (2 times less in the archive)
A ready-made library with original documentation and without extra localization files can be found here .

The latest full version of Glib can always be downloaded from this link:
http://ftp.acc.umu.se/pub/gnome/binaries/win32/glib
In this case, you will need another file intl.dll, taken from here:
http://ftp.acc.umu.se/pub/gnome/binaries/win32/dependencies/gettext-runtime_0.18.1.1-2_win32.zip
By the way, there is a 64-bit version of the library:
http://ftp.acc.umu.se/pub/gnome/binaries/win64/glib

MacOS:


How exactly things are in MacOS I do not know, but Glib is also there.

Connect to the project


Glib library is connected by one h-file:
#include <glib.h>

Linux and mingw32:

Find out the way to the h-files:
pkg-config --cflags gthread-2.0
Learn the link libraries:
pkg-config --libs gthread-2.0

Example makefile:
CC=gcc
DEBUG = -g3
RELEASE = -o3
FLAGS := $(shell pkg-config --cflags gthread-2.0)
LIBS := $(shell pkg-config --libs gthread-2.0)
SOURCES= test_app.c
test_app : $(SOURCES)
$(CC) -o $@ $(SOURCES) $(DEBUG) $(FLAGS) $(LIBS)
all : test_app
clean:
rm -f *.o


Windows:

Register paths in the project where the h-files are:
include / glib-2.0
lib / glib-2.0 / include
Connect two lib files to the project:
gthread-2.0.lib
glib-2.0.lib.

Features of use with GTK


In a multithreaded Gtk application, accessing Gtk or Gdk functions from several threads simultaneously can lead to unpredictable results, usually to a distorted graphic or to hang the application.
To circumvent this unpleasant situation, you need to place the code in all threads with calls to Gtk and Gdk functions inside a special critical section, i.e. limit the code to the following functions:
// entrance to the critical section
gdk_threads_enter ();
// exit from critical section
gdk_threads_leave ();

Example of use:

// ;
gpointer thread_func(gpointer data)
{
//
gdk_threads_enter();
gtk_label_set_text(label," ");
//
gdk_threads_leave();
return 0;
}

int main (int argc, char *argv[])
{
GtkWidget *window;
//
if(!g_thread_supported())
{
g_thread_init(NULL);
// gdk_threads_enter() gdk_threads_leave()
gdk_threads_init();
}
//
gdk_threads_enter ();
gtk_init (&argc, &argv);
// ;
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
//
// ...

//
g_thread_create(thread_func,NULL,FALSE,NULL);
gtk_main();
//
gdk_threads_leave ();
return 0;
}


I would be glad if someone article will be useful.

Source: https://habr.com/ru/post/141744/


All Articles