glib帮助文档:https://developer.gnome.org/glib/
本节包括可移植支持线程,互斥锁,锁,条件和线程私有数据
描述
线程几乎像进程一样行事,但与进程不同,一个进程的所有线程共享相同的内存。 好处是它通过这个共享内存提供了相关线程之间的简单通信,但是缺点是如果程序没有经过精心的设计,奇怪的事情(所谓的“Heisenbugs”)可能会发生。 特别是,由于线程的并发特性,除非程序员通过同步原语明确强制执行,否则不会对在不同线程中运行的代码的执行顺序进行假设。GLib中线程相关函数的目的是为编写多线程软件提供一种便携的方法。 有互斥体原语来保护对内存部分(GMutex,GRecMutex和GRWLock)的访问。 有一个工具可以使用个别位锁(g_bit_lock())。 有条件变量的原语允许线程同步(GCond)。 有线程私有数据的原语 - 每个线程都有私有实例(GPrivate)的数据。 有一次性初始化设备(GOnce,g_once_init_enter())。 最后,还有创建和管理线程的原语(GThread)。
GLib线程系统曾经用g_thread_init()初始化。 这不再是必要的。 从版本2.32开始,GLib线程系统会自动初始化,所有的线程创建函数和同步原语都可以立即使用。
请注意,即使您自己不调用g_thread_new(),也无法假定程序没有线程。 在某些情况下,GLib和GIO可以为自己的目的创建线程,例如在使用g_unix_signal_source_new()或使用GDBus时。
最初,UNIX没有线程,因此一些传统的UNIX API在线程程序中是有问题的。
一些值得注意的如下:
C库函数在静态分配的缓冲区(如strtok()或strerror())中返回数据。 对于其中的很多,有一些带有a_r后缀的线程安全变体,或者您可以查看相应的GLib API(如g_strsplit()或g_strerror())。
函数setenv()和unsetenv()以非线程安全的方式处理进程环境,并可能干扰其他线程中的getenv()调用。 请注意,getenv()调用可能隐藏在其他API的后面。 例如,GNU gettext()在封面下调用getenv()。 一般来说,最好把环境看作是只读的。 如果你绝对需要修改环境,那么在main()中尽早的做,当没有其他的线程时。
setlocale()函数更改整个进程的语言环境,影响所有线程。 对语言环境的临时更改通常会改变字符串扫描或格式化函数(如scanf()或printf())的行为。 GLib提供了许多字符串API(比如g_ascii_formatd()或g_ascii_strtod()),这些API经常可以作为替代方案使用。 或者,您可以使用uselocale()函数仅更改当前线程的语言环境。
daemon()函数以与上述不兼容的方式使用fork()。 它不应该与GLib程序一起使用。
GLib本身在内部完全是线程安全的(所有全局数据都被自动锁定),但是出于性能原因,单个数据结构实例不会自动锁定。 例如,您必须协调从多个线程访问相同的GHashTable。 这个规则的两个明显的例外是GMainLoop和GAsyncQueue,它们是线程安全的,不需要进一步的应用程序级锁定来从多个线程访问。 大多数refcounting函数(如g_object_ref())也是线程安全的。
GThreads的常见用法是将长时间运行的阻塞操作从主线程移出到工作线程。 对于GLib函数,比如单一的GIO操作,这不是必须的,并且使代码复杂化。 相反,函数的_async()版本应该从主线程中使用,而不需要在多个线程之间进行锁定和同步。 如果操作确实需要移动到工作线程,请考虑使用g_task_run_in_thread()或GThreadPool。 GThreadPool通常是比GThread更好的选择,因为它处理线程重用和任务排队; GTask在内部使用。
但是,如果需要按顺序执行多个阻塞操作,并且不能使用GTask,那么将它们移动到工作线程可以简化代码。
线程
GThreadFunc ()
指定传递给g_thread_new()或g_thread_try_new()的func函数的类型。
参数
data
数据传递给线程
返回
线程的返回值
g_thread_new ()
这个函数创建一个新的线程。 新的线程通过调用参数数据的func开始。 该线程将运行,直到func返回或直到从新线程调用g_thread_exit()。 func的返回值成为线程的返回值,可以通过g_thread_join()来获得。
该名称可以用于区分调试器中的线程。 它不用于其他目的,也不一定是唯一的。 一些系统将名称长度限制为16个字节。
如果线程无法创建,程序将中止。 如果你想尝试处理失败,请参阅g_thread_try_new()。
如果你使用线程来卸载(可能是很多)短期任务,GThreadPool可能比手动创建和跟踪多个GThread更合适。
要释放此函数返回的结构体,请使用g_thread_unref()。 请注意,g_thread_join()也隐式地取消了GThread。
参数
name
新线程的(可选)名称。[可空]
func
一个在新线程中执行的函数
data
提供给新线程的数据
返回
新的GThread
g_thread_try_new ()
这个函数与g_thread_new()相同,除了它允许失败的可能性。
如果线程无法创建(由于资源限制),则会设置错误并返回NULL。
参数
name
新线程的(可选)名称。[可空]
func
一个在新线程中执行的函数
data
提供给新线程的论据
返回
新的GThread,如果发生错误,则返回NULL
g_thread_ref ()
增加线程的引用计数。
参数
thread
GThread
返回
一个新的线程引用
g_thread_unref ()
减少线程的引用计数,可能释放与之关联的所有资源。
请注意,每个线程在运行时都会保存对GThread的引用,因此如果不再需要,可以放弃自己的引用。
参数
thread
GThread
g_thread_join ()
等待线程完成,即函数func,给g_thread_new(),返回或调用g_thread_exit()。 如果线程已经终止,那么g_thread_join()立即返回。
任何线程都可以通过调用g_thread_join()来等待任何其他线程,而不仅仅是它的“创建者”。 多个线程中调用g_thread_join()等待同一个线程会导致未定义的行为。
由func返回的值或给g_thread_exit()的值是由这个函数返回的。
g_thread_join()消耗对传入线程的引用。 这通常会导致GThread结构和相关的资源被释放。 使用g_thread_ref()来获得一个额外的引用,如果你想保持GThread超出g_thread_join()调用。
参数
thread
GThread
返回
线程的返回值
g_thread_yield ()
使调用线程自动放弃CPU,以便其他线程可以运行。
这个功能经常被用来作为一个方法使忙碌等待更少的占用。
g_thread_exit ()
终止当前线程。
如果另一个线程正在使用g_thread_join()等待我们,那么等待的线程将被唤醒并获得retval作为g_thread_join()的返回值。
使用参数retval调用g_thread_exit()等同于从函数func返回ret_val,如g_thread_new()所示。
您只能从您使用g_thread_new()或相关的API创建的线程中调用g_thread_exit()。 您不能从使用其他线程库或GThreadPool创建的线程中调用此函数。
参数
retval
这个线程的返回值
g_thread_self ()
这个函数返回对应于当前线程的GThread。 请注意,该函数不会增加返回结构的引用计数。
即使对于非GLib创建的线程(即由其他线程API创建的线程),该函数也会返回一个GThread。 这对于线程识别目的(即比较)可能是有用的,但是你不能在这些线程上使用GLib函数(比如g_thread_join())。
返回
表示当前线程的GThread
总结:glib实现了pthread基本功能,但是并没有完全涵盖pthread的函数,所以在某些时候可能会出现混用的情况。
例程如下:
#include <glib.h>#include <glib/gprintf.h>
#include <unistd.h>
#include <sys/syscall.h>
gpointer thread_func1 (gpointer data)
{
g_printf("%s %ld in\n", __func__, syscall(__NR_gettid));
g_printf("%s %ld data is : %d\n", __func__, syscall(__NR_gettid), *((gint *)data));
g_usleep (2000000);
g_printf("%s %ld out\n", __func__, syscall(__NR_gettid));
g_thread_exit ((gpointer)20);//等价于return
//return (gpointer)20;
}
int main(int argc, char **argv)
{
g_printf ("main in\n");
gint func1_data = 11;
GThread *gthread = NULL;
gthread = g_thread_new ("func1", thread_func1, &func1_data);
//gthread = g_thread_try_new ("func1", thread_func1, &func1_data);
//if(gthread == NULL) g_printf ("g_thread_try_new fail\n");
g_printf ("g_thread_join %ld\n", (gint64)g_thread_join (gthread));
g_usleep (5000000);
g_printf ("main out\n");
return 0;
}
锁
总结:锁的用法基本可linux标准锁一致,这里就翻译了,唯独特殊的是glib区分静态分配锁和动态分配锁。例程如下:
#include <glib.h>#include <glib/gprintf.h>#include <unistd.h>#include <sys/syscall.h>G_LOCK_DEFINE (thread_mutex);gint count = 0;gpointer thread_func1 (gpointer data){ gint tmp; while(1) { G_LOCK (thread_mutex); g_printf("%s %ld count: %d\n", __func__, syscall(__NR_gettid), count); tmp = count; count++; g_usleep (2000000); g_printf("%s %ld count++ tmp: %d count: %d\n", __func__, syscall(__NR_gettid), tmp, count); G_UNLOCK (thread_mutex); g_usleep (20000); }}gpointer thread_func2 (gpointer data){ gint tmp; while(1) { G_LOCK (thread_mutex); g_printf("%s %ld count: %d\n", __func__, syscall(__NR_gettid), count); tmp = count; count++; g_usleep (2000000); g_printf("%s %ld count++ tmp: %d count: %d\n", __func__, syscall(__NR_gettid), tmp, count); G_UNLOCK (thread_mutex); g_usleep (20000); }}int main(int argc, char **argv){ g_printf ("main in\n"); GThread *gthread1 = NULL, *gthread2 = NULL; gthread1 = g_thread_new ("func1", thread_func1, NULL); gthread2 = g_thread_new ("func2", thread_func2, NULL); g_thread_join (gthread1); g_thread_join (gthread2); g_printf ("main out\n"); return 0;}
线程条件
g_cond_init ()初始化一个GCond,以便可以使用它。
这个函数对初始化已经被分配为更大结构的一部分的GCond很有用。 没有必要初始化静态分配的GCond。
要在不再需要GCond的情况下撤消g_cond_init()的效果,请使用g_cond_clear()。
在已经初始化的GCond上调用g_cond_init()会导致未定义的行为。
参数
cond
一个未初始化的GCond
从:2.32
g_cond_clear ()
用g_cond_init()释放分配给GCond的资源。
这个函数不应该与静态分配的GCond一起使用。
对线程阻塞的GCond调用g_cond_clear()会导致未定义的行为。
参数
cond
一个初始化的GCond
从:2.32
void g_cond_wait ()
原子级释放互斥锁并等待直到cond被发信号。 当这个函数返回时,互斥锁再次被锁定,并由调用线程拥有。
使用条件变量时,可能会发生虚假唤醒(即:即使未调用g_cond_signal(),也会返回g_cond_wait())。 被盗的唤醒也有可能发生。 这是当调用g_cond_signal()时,另一个线程在该线程之前获取互斥体,并以g_cond_wait()能够返回的方式修改程序的状态时,不再满足预期的条件。
为此,g_cond_wait()必须始终在循环中使用。 有关完整的示例,请参阅GCond的文档。
参数
cond
一个GCond
mutex
一个当前被锁定的GMutex
gboolean g_cond_timed_wait ()
g_cond_timed_wait自版本2.32开始已被弃用,不应在新编写的代码中使用。
改用g_cond_wait_until()。
gboolean g_cond_wait_until ()
等待直到任何cond被发信号或者end_time已经过去。
与g_cond_wait()一样,可能会发生虚假或被盗的唤醒。 出于这个原因,等待一个条件变量应该总是在一个循环中,基于一个显式检查的谓词。
如果条件变量被发送(或者在虚假唤醒的情况下),则返回TRUE。 如果end_time已过,则返回FALSE。
以下代码显示了如何在条件变量上正确执行定时等待(扩展了GCond文档中提供的示例):
gpointer
pop_data_timed (void)
{
gint64 end_time;
gpointer data;
g_mutex_lock (&data_mutex);
end_time = g_get_monotonic_time () + 5 * G_TIME_SPAN_SECOND;
while (!current_data)
if (!g_cond_wait_until (&data_cond, &data_mutex, end_time))
{
// timeout has passed.
g_mutex_unlock (&data_mutex);
return NULL;
}
// there is data for us
data = current_data;
current_data = NULL;
g_mutex_unlock (&data_mutex);
return data;
}
请注意,结束时间是在进入循环并重新使用之前计算的。 这是在这个API上使用绝对时间的动机 - 如果5秒钟的相对时间直接传递给调用并发生虚假唤醒,程序将不得不重新开始等待(这会导致等待时间超过5秒)。
参数
cond
一个GCond
mutex
一个当前被锁定的GMutex
end_time
单调的时间要等到
返回
信号为TRUE,超时为FALSE
从:2.32
void g_cond_signal ()
如果线程正在等待cond,至少其中一个被解锁。 如果没有线程在等待cond,这个函数不起作用。 虽然不需要,但在调用此函数的同时保持与等待线程相同的锁定是个好习惯。
参数
cond
一个GCond
void g_cond_broadcast ()
如果线程正在等待cond,它们全部被解除阻塞。 如果没有线程在等待cond,这个函数不起作用。 虽然不需要,但在调用此函数的同时锁定与等待线程相同的互斥锁是一个好习惯。
参数
cond
一个GCond
总结:其实这部分与linux也十分相似,基本功能相同,这里就不多说。
例程如下:
#include <glib.h>#include <glib/gprintf.h>#include <unistd.h>#include <sys/syscall.h>GMutex *thread_mutex, *broad_mutex;GCond *cond1, *broad_cond;gint count = 0, broad_flag = 0;gpointer decrement (gpointer data){ g_printf("%s in\n", __func__); g_mutex_lock (thread_mutex); while (count == 0) g_cond_wait(cond1, thread_mutex); count--; g_printf("%s decrement:%d.\n", __func__, count); g_printf("out decrement.\n"); g_mutex_unlock (thread_mutex); g_printf("%s out\n", __func__); return NULL;}gpointer increment (gpointer data){ g_printf("%s in\n", __func__); g_mutex_lock (thread_mutex); count++; g_printf("%s increment:%d.\n", __func__, count); if (count != 0) g_cond_signal (cond1); g_mutex_unlock (thread_mutex); g_printf("%s out\n", __func__); return NULL;}gpointer broadcast (gpointer data){ g_printf("%s in\n", __func__); g_usleep (5000000); g_mutex_lock (broad_mutex); //todo something broad_flag = 1; g_cond_broadcast (broad_cond); g_mutex_unlock (broad_mutex); g_printf("%s out\n", __func__); return NULL;}gpointer trigger1 (gpointer data){ g_printf("%s in\n", __func__); g_mutex_lock (broad_mutex); while (broad_flag == 0) g_cond_wait(broad_cond, broad_mutex); g_printf("%s recv broad_cond\n", __func__); g_mutex_unlock (broad_mutex); g_printf("%s out\n", __func__);}gpointer trigger2 (gpointer data){ g_printf("%s in\n", __func__); g_mutex_lock (broad_mutex); while (broad_flag == 0) g_cond_wait(broad_cond, broad_mutex); g_printf("%s recv broad_cond\n", __func__); g_mutex_unlock (broad_mutex); g_printf("%s out\n", __func__);}gpointer trigger3 (gpointer data){ g_printf("%s in\n", __func__); g_mutex_lock (broad_mutex); while (broad_flag == 0) g_cond_wait(broad_cond, broad_mutex); g_printf("%s recv broad_cond\n", __func__); g_mutex_unlock (broad_mutex); g_printf("%s out\n", __func__);}int main(int argc, char **argv){ g_printf ("main in\n"); GThread *gthread1 = NULL, *gthread2 = NULL; cond1 = g_new(GCond, 1); g_cond_init (cond1); thread_mutex = g_new(GMutex, 1); g_mutex_init (thread_mutex); gthread1 = g_thread_new ("func1", decrement, NULL); g_usleep (2000000); gthread2 = g_thread_new ("func2", increment, NULL); g_thread_join (gthread1); g_thread_join (gthread2); g_mutex_clear (thread_mutex); g_cond_clear(cond1); g_printf ("----------broadcast cond test-----------\n"); GThread *gthread3 = NULL, *gthread4 = NULL; broad_cond = g_new(GCond, 1); g_cond_init (broad_cond); broad_mutex = g_new(GMutex, 1); g_mutex_init (broad_mutex); gthread1 = g_thread_new ("broadcast", broadcast, NULL); gthread2 = g_thread_new ("trigger1", trigger1, NULL); gthread3 = g_thread_new ("trigger2", trigger2, NULL); gthread4 = g_thread_new ("trigger3", trigger3, NULL); g_thread_join (gthread1); g_thread_join (gthread2); g_thread_join (gthread3); g_thread_join (gthread4); g_mutex_clear (broad_mutex); g_cond_clear(broad_cond); g_printf ("main out\n"); return 0;}
私有数据
这个功能linux也是支持的,只是极少被用到,因为这种功能总是可以使用其他方法实现。如果有兴趣的可以去参考linux的 pthread_key_create相关函数。G_PRIVATE_INIT()
一个宏来协助一个GPrivate的静态初始化。
这个宏对于GDestroyNotify函数应该与键关联的情况非常有用。 当这个键被用来指向内存时,这个是需要的,当这个线程退出时这个内存应该被释放。
此外,当使用g_private_replace()时,GDestroyNotify也将被调用存储在键中的以前的值。
如果不需要GDestroyNotify,则不需要使用此宏 - 如果GPrivate在静态作用域中声明,那么默认情况下它将被正确初始化(即:全部为零)。 看下面的例子。
理解:在线程退出时,需要执行一些销毁动作时,需要使用G_PRIVATE_INIT()注册销毁函数。在线程退出时会自动将key作为参数执行注册的函数。
参数
notify
GDestroyNotify
g_private_get ()
返回线程局部变量键的当前值。
如果该线程中尚未设置该值,则返回NULL。 值不会在线程之间复制(例如,在创建新线程时)。
参数
key
一个GPrivate
返回
线程本地值
g_private_set ()
将线程局部变量键设置为在当前线程中具有值的值。
该函数与g_private_replace()的区别在于:GDestroyNotify不会被调用于旧的值。
参数
key
一个GPrivate
value
新的价值
g_private_replace ()
将线程局部变量键设置为在当前线程中具有值的值。
这个函数与g_private_set()的区别在于:如果前面的值是非NULL,那么对其运行GDestroyNotify处理程序。
参数
key
一个GPrivate
value
新的价值
从:2.32
例程如下:
#include <glib.h>#include <glib/gprintf.h>#include <unistd.h>#include <sys/syscall.h>struct test_struct { gint i; gfloat k;};void destroy_notify(gpointer a){ g_printf ("destroy_notify in\n"); //此处应该执行释放动态内存动作 g_printf ("destroy_notify out\n");}static GPrivate private_key = G_PRIVATE_INIT (destroy_notify);gint count = 0;gpointer thread_func1 (gpointer data){ struct test_struct *struct_data = g_new(struct test_struct, 1); struct_data->i = 10; struct_data->k = 3.1415; g_private_set (&private_key, (gpointer)(struct_data)); g_printf ("%s struct_data addr: 0x%p\n", __func__, struct_data); g_printf ("%s g_private_get(key) return addr: 0x%p\n", __func__, (struct test_struct *)g_private_get (&private_key)); g_printf ("%s g_private_get(key) into struct_data.i: %d\nstruct_data.k: %f\n", __func__, ((struct test_struct *)g_private_get (&private_key))->i, ((struct test_struct *)g_private_get(&private_key))->k); g_usleep (2000000);}gpointer thread_func2 (gpointer data){ gint temp = 20; g_usleep (1000000); g_printf ("%s temp addr: 0x%p\n", __func__, &temp); g_private_set (&private_key, GINT_TO_POINTER (temp)); g_printf ("%s g_private_get(key): 0x%p\n", __func__, (gint *)g_private_get(&private_key)); g_printf ("%s g_private_get(key) temp: %d\n", __func__, GPOINTER_TO_INT (g_private_get(&private_key)));}int main(int argc, char **argv){ g_printf ("main in\n"); GThread *gthread1 = NULL, *gthread2 = NULL; gthread1 = g_thread_new ("func1", thread_func1, NULL); gthread2 = g_thread_new ("func2", thread_func2, NULL); g_thread_join (gthread1); g_thread_join (gthread2); g_printf ("main out\n"); return 0;}
一次性初始化
#define g_once()具有给定的GOnce结构的进程对该例程的第一次调用将使用给定的参数调用func。 此后,使用相同的GOnce结构对g_once()的后续调用不会再次调用func,而是返回第一个调用的存储结果。 从g_once()返回时,一次的状态将是G_ONCE_STATUS_READY。
例如,一个互斥体或一个线程特定的数据键必须被创建一次。 在线程环境中,调用g_once()可确保初始化跨多个线程进行串行化。
在func中的同一个GOnce结构上递归地调用g_once()会导致死锁。
gpointer
get_debug_flags (void)
{
static GOnce my_once = G_ONCE_INIT;
g_once (&my_once, parse_debug_flags, NULL);
return my_once.retval;
}
参数
once
一个GOnce结构
func
GThreadFunc函数关联一次。 这个函数只被调用一次,而不管它和它相关的GOnce结构被传递给g_once()的次数。
arg
数据传递给func
从:2.4
gboolean g_once_init_enter ()
启动临界初始化部分时要调用的函数。 参数的位置必须指向一个静态的0初始化变量,它将在初始化部分的最后被设置为一个非零值。 结合g_once_init_leave()和唯一地址value_location,可以确保初始化部分在程序生命周期中只执行一次,并发线程被阻塞,直到初始化完成。 用于像这样的构造:
static gsize initialization_value = 0;
if (g_once_init_enter (&initialization_value))
{
gsize setup_value = 42; // initialization code here
g_once_init_leave (&initialization_value, setup_value);
}
// use initialization_value here
参数
location
包含0的静态可初始化变量的位置。[不可空]
返回
如果应该输入初始化部分,则为TRUE,否则为FALSE
从:2.14
void g_once_init_leave ()
与g_once_init_enter()相对应。 需要一个静态初始化变量0的初始化位置,以及初始化值不是0的位置。将该变量设置为初始化值,并释放g_once_init_enter()中对此初始化变量的并发线程阻塞。
参数
location
包含0的静态可初始化变量的位置。[不可空]
result
* value_location的新的非0值
从:2.14
整数指针的原子操作锁
void g_bit_lock ()
gboolean g_bit_trylock ()
void g_bit_unlock ()
void g_pointer_bit_lock ()
gboolean g_pointer_bit_trylock ()
void g_pointer_bit_unlock ()
guint g_get_num_processors ()
确定系统将为此过程同时安排的近似线程数。 这是为了用作g_thread_pool_new()用于CPU绑定任务和类似情况的参数。
总结:这部分linux也有函数,有兴趣的可以参考pthread_once相关函数。