При закрытии подписчики были переданы в рассылку "Обзор инструментов SEO-оптимизатора и методов продвижения" на которую и рекомендуем вам подписаться.
Вы можете найти рассылки сходной тематики в Каталоге рассылок.
Работа с потоками Вначале хочу сказать, что теперь все выпуски можно найти в одном файле gtk_book.chm. Использование Glib(входит в состав GTK+) при написании многопоточных программ имеет свои плюсы. Самое очевидное – это переносимость, одно и то же приложение может быть совместимо с Unix, Windows и MacOS на уровне исходных кодов. К тому же Glib есть на всех Linux дистрибутивах, даже там, где нет GTK, да и для Windows приходится носить с собой гораздо меньше DLL библиотек, чем для всего GTK целиком. Если я Вас убедил использовать Glib для создания многопоточных программ, тогда читайте дальше.
Рассмотрим создание потоков, и синхронизацию между одновременно выполняемыми потоками с помощью мьютексов и условий(событий).
Где-нибудь в начале приложения нужно вызвать функцию инициализации работы с потоками:
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); }
Для синхронизации между потоками можно пользоваться мьютексами, которые позволяют выполнять последовательный доступ к совместным ресурсам из разных потоков.
Создание мьютекса: 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; }
Условия также служат для синхронизации потоков, с их помощью можно устанавливать условия(события) и ждать их установки в другом потоке(бесконечно или не более заданного времени).
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). Единственное, на мой взгляд, чего не хватает, так это возможности принудительно прервать выполняемый поток из другого потока. Хотя, это может и правильно, грамотно написанная программа всегда будет иметь возможность штатным образом завершить поток.
|
В избранное | ||