关于linux:在Linux中使用线程

38次阅读

共计 3574 个字符,预计需要花费 9 分钟才能阅读完成。

1. 第一个例子

在 Linux 下创立的线程的 API 接口是 pthread_create(),它的残缺定义是:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*) void *arg);

当你的程序调用了这个接口之后,就会产生一个线程,而这个线程的入口函数就是 start_routine()。如果线程创立胜利,这个接口会返回 0。

start_routine() 函数有一个参数,这个参数就是 pthread_create 的最初一个参数 arg。这种设计能够在线程创立之前就帮它筹备好一些专有数据,最典型的用法就是应用 C ++ 编程时的 this 指针。start_routine() 有一个返回值,这个返回值能够通过 pthread_join() 接口取得。

pthread_create() 接口的第一个参数是一个返回参数。当一个新的线程调用胜利之后,就会通过这个参数将线程的句柄返回给调用者,以便对这个线程进行治理。

pthread_create() 接口的第二个参数用于设置线程的属性。这个参数是可选的,当不须要批改线程的默认属性时,给它传递 NULL 就行。具体线程有那些属性,咱们前面再做介绍。

将这段代码保留为 thread.c 文件,能够执行上面的命令来生成可执行文件:

$ gcc thread.c -o thread -lpthread

这段代码的执行后果可能是这样:

$ ./thread

This is the main process.

This is a thread and arg = 10.

thread_ret = 0.

留神,我说的是可能有这样的后果,在不同的环境下可能会有出入。因为这是多线程程序,线程代码可能先于第 24 行代码被执行。

咱们回过头来再剖析一下这段代码。在第 18 行调用 pthread_create() 接口创立了一个新的线程,这个线程的入口函数是 start_thread(),并且给这个入口函数传递了一个参数,且参数值为 10。这个新创建的线程要执行的工作非常简单,只是将显示“This is a thread and arg = 10”这个字符串,因为 arg 这个参数值曾经定义好了,就是 10。之后线程将 arg 参数的值批改为 0,并将它作为线程的返回值返回给零碎。与此同时,主过程做的事件就是持续判断这个线程是否创立胜利了。在咱们的例子中基本上没有创立失败的可能。主过程会持续输入“This is the main process”字符串,而后调用 pthread_join() 接口与方才的创立进行合并。这个接口的第一个参数就是新创建线程的句柄了,而第二个参数就会去承受线程的返回值。pthread_join() 接口会阻塞主过程的执行,直到合并的线程执行完结。因为线程在完结之后会将 0 返回给零碎,那么 pthread_join() 取得的线程返回值天然也就是 0。输入后果“thread_ret = 0”也证实了这一点。

那么当初有一个问题,那就是 pthread_join() 接口干了什么? 什么是线程合并呢?

2. 线程的合并与拆散

咱们首先要明确的一个问题就是什么是线程的合并。从后面的叙述中读者们曾经理解到了,pthread_create() 接口负责创立了一个线程。那么线程也属于零碎的资源,这跟内存没什么两样,而且线程自身也要占据肯定的内存空间。家喻户晓的一个问题就是 C 或 C ++ 编程中如果要通过 malloc() 或 new 调配了一块内存,就必须应用 free() 或 delete 来回收这块内存,否则就会产生驰名的内存透露问题。既然线程和内存没什么两样,那么有创立就必须得有回收,否则就会产生另外一个驰名的资源透露问题,这同样也是一个重大的问题。那么线程的合并就是回收线程资源了。

在 Linux 下创立的线程的 API 接口是 pthread_create(),它的残缺定义是:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*) void *arg);

当你的程序调用了这个接口之后,就会产生一个线程,而这个线程的入口函数就是 start_routine()。如果线程创立胜利,这个接口会返回 0。

start_routine() 函数有一个参数,这个参数就是 pthread_create 的最初一个参数 arg。这种设计能够在线程创立之前就帮它筹备好一些专有数据,最典型的用法就是应用 C ++ 编程时的 this 指针。start_routine() 有一个返回值,这个返回值能够通过 pthread_join() 接口取得。

pthread_create() 接口的第一个参数是一个返回参数。当一个新的线程调用胜利之后,就会通过这个参数将线程的句柄返回给调用者,以便对这个线程进行治理。

pthread_create() 接口的第二个参数用于设置线程的属性。这个参数是可选的,当不须要批改线程的默认属性时,给它传递 NULL 就行。具体线程有那些属性,咱们前面再做介绍。

好,那么咱们就利用这些接口,来实现在 Linux 上的第一个多线程程序,见代码 1 所示:

线程的合并是一种被动回收线程资源的计划。当一个过程或线程调用了针对其它线程的 pthread_join() 接口,就是线程合并了。这个接口会阻塞调用过程或线程,直到被合并的线程完结为止。当被合并线程完结,pthread_join() 接口就会回收这个线程的资源,并将这个线程的返回值返回给合并者。

与线程合并绝对应的另外一种线程资源回收机制是线程拆散,调用接口是 pthread_detach()。线程拆散是将线程资源的回收工作交由零碎主动来实现,也就是说当被拆散的线程完结之后,零碎会主动回收它的资源。因为线程拆散是启动零碎的主动回收机制,那么程序也就无奈取得被拆散线程的返回值,这就使得 pthread_detach() 接口只有领有一个参数就行了,那就是被拆散线程句柄。

线程合并和线程拆散都是用于回收线程资源的,能够依据不同的业务场景酌情应用。不论有什么理由,你都必须抉择其中一种,否则就会引发资源透露的问题,这个问题与内存透露同样可怕。

线程创立时默认 joinable 状态,如果不显示 join 阻塞调用或者设置拆散状态,即使线程完结返回或者 pthread_exit 时都不会开释线程所占用堆栈和线程描述符,造成资源透露。除了线程创立时设置拆散参数之外,还能够 detach。实现形式有两种:父线程创立完子线程后,执行 pthread_detach(tid); 或者,子线程代码里执行 pthread_detach(pthread_self())。

3. 线程本地存储

内线程之间能够共享内存地址空间,线程之间的数据交换能够十分快捷,这是线程最显著的长处。然而多个线程访问共享数据,须要低廉的同步开销,也容易造成与同步相干的 BUG,更麻烦的是有些数据基本就不心愿被共享,这又是毛病。堪称:“成也萧何,败也萧何”,说的就是这个情理。

C 程序库中的 errno 是个最典型的一个例子。errno 是一个全局变量,会保留最初一个零碎调用的错误代码。在单线程环境并不会呈现什么问题。然而在多线程环境,因为所有线程都会有可能批改 errno,这就很难确定 errno 代表的到底是哪个零碎调用的错误代码了。这就是有名的“非线程平安 (Non Thread-Safe)”的。

此外,从古代技术角度看,在很多时候应用多线程的目标并不是为了对共享数据进行并行处理 (在 Linux 下有更好的计划,前面会介绍)。更多是因为多外围 CPU 技术的引入,为了充分利用 CPU 资源而进行并行运算 (不相互烦扰)。换句话说,大多数状况下每个线程只会关怀本人的数据而不须要与他人同步。

为了解决这些问题,能够有很多种计划。比方应用不同名称的全局变量。然而像 errno 这种名称曾经固定了的全局变量就没方法了。在后面的内容中提到在线程堆栈中调配局部变量是不在线程间共享的。然而它有一个弊病,就是线程外部的其它函数很难拜访到。目前解决这个问题的简便易行的计划是线程本地存储,即 Thread Local Storage,简称 TLS。利用 TLS,errno 所反映的就是本线程内最初一个零碎调用的错误代码了,也就是线程平安的了。

上面分享一些视频·材料,提供大家学习,也能够加裙交换:1126743406
linux 根底
http://www.makeru.com.cn/cour…
linux 之 C 语言内存治理
http://www.makeru.com.cn/live…

正文完
 0