Glib学习(14) 线程 Threads

glib源码下载:http://ftp.gnome.org/pub/gnome/sources/glib/

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相关函数。


猜你喜欢

转载自blog.csdn.net/andylauren/article/details/79307483