Практические советы по GTK+

  Все выпуски  

Работа с потоками


Работа с потоками

Вначале хочу сказать, что теперь все выпуски можно найти в одном файле gtk_book.chm.

Использование Glib(входит в состав GTK+) при написании многопоточных программ имеет свои плюсы.

Самое очевидное – это переносимость, одно и то же приложение может быть совместимо с Unix, Windows и MacOS на уровне исходных кодов. К тому же Glib есть на всех Linux дистрибутивах, даже там, где нет GTK, да и для Windows приходится носить с собой гораздо меньше DLL библиотек, чем для всего GTK целиком. Если я Вас убедил использовать Glib для создания многопоточных программ, тогда читайте дальше.

 

Рассмотрим создание потоков, и синхронизацию между одновременно выполняемыми потоками с помощью мьютексов и условий(событий).

 

Потоки (Threads)

Где-нибудь в начале приложения нужно вызвать функцию инициализации работы с потоками:

 

g_thread_init(NULL);

 

 

Для создания потока достаточно одной из двух функий:

 

GThread* g_thread_create        (GThreadFunc func,

                                gpointer data,

                                gboolean joinable,

                                GError **error);

 

GThread* g_thread_create_full        (GThreadFunc func,

                                gpointer data,

                                gulong stack_size,

                                gboolean joinable,

                                gboolean bound,

                                GThreadPriority priority,

                                GError **error);

 

Параметры:

func – потоковая функция, прототип: gpointer  GThreadFunc (gpointer data);

data – указатель на дополнительные данные передаваемые в поток.

stack_size - размер стека в потоке. Если рано 0, тогда размер  стека будет минимально допустимым.

joinable - можно ли будет потом ожидать завершение потока через g_thread_join(GThread *thread);

bound - если TRUE, то поток будет системным. В win32 не на что не влияет.

priority  - приоритет нового потока.

error - сюда пишется ошибка при создании потока

 

joinable: если FALSE, тогда при вызове функции g_thread_join(GThread *thread) в основном потоке, она сразу же вернёт управление

Если TRUE, тогда функция g_thread_join(GThread *thread) будет висеть до тех пор, пока поток не завершиться.

 

priority:

принимаемые значения:

G_THREAD_PRIORITY_LOW    - низкий

G_THREAD_PRIORITY_NORMAL - нормальный (по умолчанию для g_thread_create)

G_THREAD_PRIORITY_HIGH   - высокий

G_THREAD_PRIORITY_URGENT - реального времени

Первая более простая в использовании,  но с её помощью нельзя задать приоритет выполняемого потока.

Но это не страшно, ведь всегда можно поменять приоритет потока:

void g_thread_set_priority (GThread *thread,GThreadPriority priority);

 

 

Пример создания потока с нормальным приоритетом и передачей параметров в новый поток.

 

// потоковая функция;

gpointer   thread_func(gpointer data)

{

       char *string = (char*)data;

       printf("сообщение из потока: %s\n",string);

       // Что-нибудь делаем ещё ...

       // выход из потока с кодом ошибки 10

       g_thread_exit(GINT_TO_POINTER(10));

       // до сюда не должно дойти выполнение

       return NULL;

}

 

void main()

{

       int exit_cod;

       GError *error=NULL;

       GThread *thread_id;

       char *string = "тестовая строка";

       if(!g_thread_supported())

               g_thread_init(NULL);

       // простой способ создания потока

       //thread_id = g_thread_create(thread_func,data,TRUE,NULL);

       // более сложный вариант - приведёт к тому же результату

       thread_id = g_thread_create_full(thread_func,data,0,TRUE,FALSE,G_THREAD_PRIORITY_NORMAL,&error);

       if (error)

       {

      printf("Не могу создать поток: %s", error->message);

      return 0;

       }

       printf("сообщение из основной программы: %s\n",string);

       // ждём окончания фонового потока

       exit_cod = g_thread_join(thread_id);

       printf("поток завершён с кодом: %d\n",exit_cod);

}

 

 

Мьютексы (Mutex)

Для синхронизации между потоками можно пользоваться мьютексами, которые позволяют выполнять последовательный доступ к совместным ресурсам из разных потоков.

 

Создание мьютекса:

GMutex* g_mutex_new();

 

Блокировка мьютекса:

void g_mutex_lock(GMutex *mutex);

 

Pазблокировка мьютекса:

void g_mutex_unlock(GMutex *mutex);

 

Удаление мьютекса:

void g_mutex_free(GMutex *mutex);

 

 

Пока мьютекс заблокирован, в других потоках функция g_mutex_lock() будет  ждать разблокировки мьютекса, причём повторный вызов g_mutex_lock() в том же потоке будет проигнорирован, что защищает от зависаний приложения в некорректно написанных программах.

Для блокировки есть ещё одна функция: gboolean g_mutex_trylock(GMutex *mutex); которая пытается заблокировать мьютекс. Если мьютекс свободен, то он блокируется и функция возвращает TRUE, в противном случае функция сразу завершает свою работу с кодом возврата FALSE.

 

Пример:

void func_critical (void)

{

static GMutex *mutex = NULL;

 

  if (!mutex)

    mutex = g_mutex_new();

  g_mutex_lock(mutex);

  // вычисления

  //..

  g_mutex_unlock(mutex);

}

 

Ещё есть статические мьютексы, которые создаются на этапе компиляции программы, а не во время выполнения(что может сэкономить немного времени при запуске программы); для них даже есть макросы:

int num=1;

G_LOCK_DEFINE (num);        // определение номера мьютекса (выполняется один раз)

G_LOCK(num);                // блокировка мьютекса

G_UNLOCK (num);                // разблокировка мьютекса

 

Пример использования статического мьютекса:

G_LOCK_DEFINE(1);//  для разнесения по времени проверки наличия файла и его удаления/создания

// проверка наличия файла

gboolean is_file(char *filename)

{

       int result = FALSE;

       G_LOCK(1);

       result = g_file_test(filename,G_FILE_TEST_EXISTS);

       G_UNLOCK(1)

       return result;

}

// создание файла

FILE* create_file(char *filename)

{

       FILE *file;

       G_LOCK(1);

       file = g_fopen(filename,"w");

       G_UNLOCK(1)

       return file;

}

 

 

Условия (Condition)

Условия также служат для синхронизации потоков, с их помощью можно устанавливать условия(события) и ждать их установки в другом потоке(бесконечно или не более заданного времени).

 

GCond* g_cond_new(void); - Создание условия(события)

void g_cond_signal (GCond *cond); - установить(разбудить) условие(событие)

void g_cond_wait (GCond *cond, GMutex *mutex); - ждать установки условия бесконечно.

void g_cond_timed_wait (GCond *cond, GMutex *mutex, GTimeVal *abs_time); - ждать установки условия не более заданного времени

 

Условия тесно связаны со мьютексами, установка или ожидание условия должны быть выполнены внутри мьютекса, причём функции ожидания g_cond_wait() или g_cond_timed_wait() временно разблокируют мьютекс внутри себя, позволяя зайти во мьютекс из другого потока для установки условия. Если что-то непонятно, то после того как взгляните на пример, всё должно стать ясно.

 

Пример использования: Рассмотрим случай запуска потока и ожидания его запуска с родительском потоке с помощью условий.

 

static GMutex        *g_thread_start_mutex = NULL;// для g_thread_start_cond

static GCond        *g_thread_start_cond = NULL;// для ожидания запуска потока

 

// Создание потока и ожидание его запуска с помощью условий

void start_thread()

{

       if(!g_thread_start_mutex)        g_thread_start_mutex = g_mutex_new();

       if(!g_thread_start_cond)        g_thread_start_cond = g_cond_new();

       // запускаем поток

       g_thread_create(my_thread,(void*)NULL,TRUE,NULL);

       // ждём запуска потока

       g_mutex_lock(g_thread_start_mutex);

       g_get_current_time(&timeval);// узнаём текущее время

       g_time_val_add(&timeval,2*G_USEC_PER_SEC);// добавляем 2 секунды

       // ждём не более 2 секунд

       return_val = g_cond_timed_wait(g_thread_start_cond, g_thread_start_mutex,&timeval);// временно позволяет зайти в мьютекс g_thread_start_mutex

       g_mutex_unlock(g_thread_start_mutex);

}

 

// потоковая функция;

gpointer my_thread(gpointer data)

{

       // Установить g_thread_start_cond

       g_mutex_lock (g_thread_start_mutex);

       g_cond_signal(g_thread_start_cond);

       g_mutex_unlock (g_thread_start_mutex);// сигнал установится только после покидания мьютекса

       // выполнение чего-нибудь

       // ...

       return 0;

}

 

 

 

Есть ещё другие способы синхронизации потоков, которые Вы можете найти в документации на Glib (файл glib-Threads.html).

Единственное, на мой взгляд, чего не хватает, так это возможности принудительно прервать выполняемый поток из другого потока. Хотя, это может и правильно, грамотно написанная программа всегда будет иметь возможность штатным образом завершить поток.

 


В избранное