终于要讲到线程部分,线程和进程让人够头痛的内容。
一、线程概念
老样子,我们还是按我们讲进程时的方式说起,参看:UNIX再学习 -- 进程环境
首先需要了解下,什么是线程。
Linux 下的线程,可能我们比较陌生,但是我们一直在玩 Windows 系统。应用程序文件、任务管理器,这些东西应该是很溜的。比如:
通过上图我们可以看到 一个进程中有多个线程。但是到底什么是线程呢?
参看:进程与线程的一个简单解释
大神举得例子很有意思。
进程好似车间,线程好似车间工人;任务是有很多工人协同完成;车间内的空间设施工人都是共享的;有些房间比如厕所一次只能容纳一人,进入需加锁(互斥锁)。
进程,是资源分配单位。线程,是CPU调度基本单位。
线程就是程序的执行路线,即进程内部的控制序列,或者说是进程的子任务。
一个进程可以同时拥有多个线程,即同时被系统调度的多条执行路线,但至少要有一个主线程。
一个进程的所有线程都共享进程的代码区、数据区、堆区(注意没有栈区)、环境变量和命令行参数、文件描述符、信号处理函数、当前目录、用户 ID 和组 ID 等。
一个进程的每个线程都拥有独立的 ID、寄存器值、栈内存、调度策略和优先级、信号掩码、errno变量以及线程私有数据等。
也可以说线程是包含在进程中的一种实体。它有自己的运行线索,可完成特定任务。可与其他线程共享进程中的共享变量及部分环境。可通过相互之间协同来完成进程所要完成的任务。
之前有转载一篇文章,可当扩展来看,参看:进程与线程及其区别
1.进程和线程的区别
什么是进程(Process):普通的解释就是,进程是程序的一次执行,而什么是线程(Thread),线程可以理解为进程中的执行的一段程序片段。在一个多任务环境中下面的概念可以帮助我们理解两者间的差别:进程间是独立的,这表现在内存空间,上下文环境;线程运行在进程空间内。一般来讲(不使用特殊技术)进程是无法突破进程边界存取其他进程内的存储空间;而线程由于处于进程空间内,所以同一进程所产生的线程共享同一内存空间。 同一进程中的两段代码不能够同时执行,除非引入线程。线程是属于进程的,当进程退出时该进程所产生的线程都会被强制退出并清除。线程占用的资源要少于进程所占用的资源。 进程和线程都可以有优先级。在线程系统中进程也是一个线程。可以将进程理解为一个程序的第一个线程。
线程是指进程内的一个执行单元,也是进程内的可调度实体.与进程的区别:
(1)地址空间:进程内的一个执行单元;进程至少有一个线程;它们共享进程的
地址空间;而进程有自己独立的地址空间;
(2)进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
(3)线程是处理器调度的基本单位,但进程不是.
(4)二者均可并发执行.
什么是进程(Process):普通的解释就是,进程是程序的一次执行,而什么是线程(Thread),线程可以理解为进程中的执行的一段程序片段。在一个多任务环境中下面的概念可以帮助我们理解两者间的差别:进程间是独立的,这表现在内存空间,上下文环境;线程运行在进程空间内。一般来讲(不使用特殊技术)进程是无法突破进程边界存取其他进程内的存储空间;而线程由于处于进程空间内,所以同一进程所产生的线程共享同一内存空间。 同一进程中的两段代码不能够同时执行,除非引入线程。线程是属于进程的,当进程退出时该进程所产生的线程都会被强制退出并清除。线程占用的资源要少于进程所占用的资源。 进程和线程都可以有优先级。在线程系统中进程也是一个线程。可以将进程理解为一个程序的第一个线程。
线程是指进程内的一个执行单元,也是进程内的可调度实体.与进程的区别:
(1)地址空间:进程内的一个执行单元;进程至少有一个线程;它们共享进程的
地址空间;而进程有自己独立的地址空间;
(2)进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
(3)线程是处理器调度的基本单位,但进程不是.
(4)二者均可并发执行.
1.进程和线程的差别。
答:线程是指进程内的一个执行单元,也是进程内的可调度实体.
与进程的区别:
(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
(2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行
(3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于
进程的资源.
(4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销
明显大于创建或撤消线程时的开销。
答:线程是指进程内的一个执行单元,也是进程内的可调度实体.
与进程的区别:
(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
(2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行
(3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于
进程的资源.
(4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销
明显大于创建或撤消线程时的开销。
二、POSIX 线程
早期 UNIX 厂商各自提供私有的线程库版本,无论是接口还是实现,差异都非常大,代码移植非常困难。
我们将要讨论的线程接口来自 POSIX.1-2001。线程接口也称为“pthread”或“POSIX 线程”。
POSIX 线程的功能测试宏是 _POSIX_THREADS。应用程序可以把这个宏用于 #ifdef 测试,从而在编译时确定是否支持线程,也可以把 _SC_THREADS 常数用于调用 sysconf 函数,进而在运行时确定是否支持线程。遵循 SUSv4 的系统定义符号 _POSIX_THREADS 的值为 200809L。
查看 /usr/include/i386-linux-gnu/bits/posix_opt.h 可以看到 _POSIX_THREADS 的定义
-
69
/* Tell we have POSIX threads. */
-
70
#define _POSIX_THREADS 200809L
使用 pthread 需要包含一个头文件:pthread.h
同时连一个共享库:libpthread.so 即 gcc 编译时加选项 -lpthread
-
#include <pthread.h>
-
-
gcc ... -lpthread
三、线程标识
就像每个进程有一个进程 ID 一样,每个线程也有一个线程 ID。进程 ID 在整个系统中是唯一的,但线程 ID 不同,线程 ID 只有在它所属的进程上下文中才有意义。
线程 ID 是用 pthread_t 数据类型表示的.
查看 /usr/include/i386-linux-gnu/bits/pthreadtypes.h 可以看到 pthread_t 类型为无符号长整型
typedef unsigned long int pthread_t;
实现的时候可以用一个结构来代表 pthread_t 数据类型,所以可移植的操作系统实现不能把它作为整数处理。
因此必须使用一个函数来对两个线程 ID 进行比较。
1、函数 pthread_equal
-
#include <pthread.h>
-
int pthread_equal(pthread_t t1, pthread_t t2);
-
返回值:若相等,返回非
0 数值;否则,返回
0
(1)函数功能
比较线程 ID
(2)示例说明
-
//示例一
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <pthread.h>
-
-
int main(){
-
pthread_t thread_id;
-
-
thread_id=pthread_self();
// 返回调用线程的线程ID
-
printf(
"Thread ID: %lu.\n",thread_id);
-
-
if (pthread_equal(thread_id,pthread_self()))
-
{
-
printf(
"Equal!\n");
-
}
else {
-
printf(
"Not equal!\n");
-
}
-
return
0;
-
}
-
编译:
# gcc test.c -lpthread
-
输出结果:
-
Thread ID:
3075704512.
-
Equal!
-
//示例二
-
#include <stdio.h>
-
#include <pthread.h>
-
#include <stdlib.h>
-
#include <string.h>
-
#include <unistd.h>
-
-
void* routine1 (void* arg)
-
{
-
pthread_t thread = pthread_self ();
-
printf (
"子线程1 ID:%lu\n", thread);
-
-
return (
void*)thread;
-
}
-
-
void* routine2 (void* arg)
-
{
-
pthread_t thread = pthread_self ();
-
printf (
"子线程2 ID:%lu\n", thread);
-
-
if (pthread_equal (thread, (
pthread_t)arg))
-
printf (
"两个线程ID相同\n");
-
else
-
printf (
"两个线程ID不同\n");
-
-
return (
void*)thread;
-
}
-
-
int main()
-
{
-
pthread_t thread1;
-
int error = pthread_create (&thread1,
NULL, routine1,
NULL);
-
if (error)
-
{
-
perror (
"pthread_create");
-
exit (EXIT_FAILURE);
-
}
-
-
pthread_join (thread1,
NULL);
-
-
pthread_t thread2;
-
error = pthread_create (&thread2,
NULL, routine2, (
void*)&thread1);
-
if (error)
-
{
-
perror (
"pthread_create");
-
exit (EXIT_FAILURE);
-
}
-
-
//sleep (1);
-
pthread_join (thread2,
NULL);
-
-
return
0;
-
}
-
输出结果:
-
子线程
1 ID:
3076029248
-
子线程
2 ID:
3076029248
-
两个线程ID不同
(3)示例解析
pthread_equal 函数比较两个线程 ID,这个没什么可讲的。
示例二是我讲完以后又添加的,创建了两个子线程。它们的线程 ID 不同,可以理解。
我要说的是 sleep 和 pthread_join 有什么区别呢? 还有为什么在线程中使用 usleep 不合适?
留个疑问,后续我们讲多线程会一起讲到的!!
2、函数 pthread_self
-
#include <pthread.h>
-
pthread_t pthread_self(
void);
-
返回值:调用线程的线程 ID
(1)函数功能
获取线程自身的 ID
(2)示例说明
当线程需要识别以线程 ID 作为标识的数据结构时,pthread_self 函数可以与 pthread_equal 函数一起使用,
如上例
-
#include <pthread.h>
-
#include <stdio.h>
-
-
void* thread_func(void *arg)
-
{
-
printf(
"thread id=%lu\n", pthread_self());
-
return arg;
-
}
-
-
int main(void)
-
{
-
pid_t pid;
-
pthread_t tid;
-
pid = getpid();
-
printf(
"process id=%lu\n", pid);
-
pthread_create(&tid,
NULL, thread_func,
NULL);
-
pthread_join(tid,
NULL);
-
return
0;
-
}
-
输出结果:
-
process id=
2933
-
thread id=
3076057920
(3)示例解析
创建线程,查看线程自身的线程 ID
四、线程创建
1、函数 pthread_create
-
#include <pthread.h>
-
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
-
void *(*start_routine)
(void *),
void *arg);
-
返回值:若成功,返回
0;否则,返回错误编号
(1)函数功能
创建新线程
(2)参数解析
thread:输出线程 ID。pthread_t 即 unsigned long int。
attr:线程属性,NULL 表示缺省属性。pthread_attr_t 可能是整型也可能是结构体,因实现而异。
start_routine:线程过程函数指针。参数和返回值的类型都是 void*。启动线程过程其实就是调用一个函数,只不过是在一个独立的线程中调用,函数一旦返回,线程即告结束。
start_routine:线程过程函数指针。参数和返回值的类型都是 void*。启动线程过程其实就是调用一个函数,只不过是在一个独立的线程中调用,函数一旦返回,线程即告结束。
arg:传递给线程过程函数的参数。线程过程函数的调用者是系统内核,因此需要预先将参数存储到系统内核中。
(3)函数解析
main 函数可以被视为主线程的线程过程函数。main函数一旦返回,主线程即告结束。主线程一旦结束,进程即告结束。进程一旦结束,其所有的子线程统统结束。
应设法保证在线程过程函数执行期间,传递给它的参数 arg 所指向的目标持久有效。
注意,pthread 函数在调用失败时通常会返回错误码,它们并不像其他的 POSIX 函数一样设置 errno。每个线程都提供 errno 的副本,这只是为了与使用 errno 的现有函数兼容。子啊线程中,从函数中返回错误码更为清晰整洁,不需要依赖那些随着函数执行不断变化的全局状态,这样可以把错误的范围限制在引起出错的函数中。
(4)示例说明
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <pthread.h>
-
void *task (void* arg)
-
{
-
printf (
"进入子线程\n");
-
sleep (
5);
-
printf (
"子线程进程ID是:%lu\n", getpid ());
-
printf (
"22子线程ID是:%lu\n", pthread_self ());
-
printf (
"退出子线程\n");
-
}
-
-
int main (void)
-
{
-
printf (
"主线程启动\n");
-
pthread_t thread;
-
int error = pthread_create (&thread,
NULL, task,
NULL);
-
if (error)
-
perror (
"pthread_create"),
exit (
1);
-
sleep (
10);
-
printf (
"退出主线程\n");
-
-
printf(
"11子线程ID是:%lu\n",thread);
-
printf (
"主线程进程ID是:%lu\n", getpid ());
-
printf(
"主线程ID是:%lu\n",pthread_self());
-
return
0;
-
}
-
编译:
# gcc test.c -lpthread
-
输出结果:
-
主线程启动
-
进入子线程
-
子线程进程ID是:
3117
-
22子线程ID是:
3076180800
-
退出子线程
-
退出主线程
-
11子线程ID是:
3076180800
-
主线程进程ID是:
3117
-
主线程ID是:
3076183744
(5)示例解析
主线程需要等待子线程结束后再结束,如果不等待不如把上例的 sleep (10); 注释掉,则不执行子线程。
子线程和主线程是同一个进程,thread 为输出子线程 ID,pthread_self函数可以得到线程自身的线程ID
再有 pthread 函数在调用失败时通常会返回错误码。
五、线程的等待
1、函数 pthread_join
-
#include <pthread.h>
-
int pthread_join(pthread_t thread, void **retval);
-
返回值:成功返回
0,失败返回错误号
(1)函数功能
主要用于等待一个线程的结束,并且回去退出码
(2)参数解析
第一个参数:线程的 ID
第二个参数:二级指针,用于获取线程的退出码
(3)函数解析
该函数根据参数 thread 指定的线程进程等待,将目标线程终止时的退出状态信息拷贝到 *retval 这个参数指定的位置上。
pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是 joinable 的。
(4)示例说明
-
//示例一
-
#include <stdio.h>
-
#include <pthread.h>
-
-
void *task (void *p)
-
{
-
//ps 指向只读常量区 ps本身在栈区
-
char *ps =
"hello";
-
return ps;
-
}
-
-
int main (void)
-
{
-
//启动一个线程
-
pthread_t tid;
-
pthread_create (&tid,
NULL, task,
NULL);
-
//主线程等待子线程的处理,并且获取返回值
-
char *pc =
NULL;
-
//pc 指针指向了 上面字符串的首地址
-
pthread_join (tid, (
void**)&pc);
-
printf (
"pc = %s\n", pc);
-
return
0;
-
}
-
输出结果:
-
pc = hello
-
//示例二
-
//使用pthread_join函数获取线程的返回值
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <unistd.h>
-
#include <pthread.h>
-
-
void* task(void* p)
-
{
-
int i =
0;
-
//静态局部变量,生命周期变长
-
static
int sum =
0;
-
for(i =
1; i <=
100; i++)
-
{
-
sum += i;
-
}
-
return (
void*)∑
-
}
-
-
int main(
void)
-
{
-
//1.启动一个线程
-
pthread_t tid;
-
pthread_create(&tid,
NULL,task,
NULL);
-
//2.主线程进行等待,获取返回值
-
int* pi =
NULL;
-
pthread_join(tid,(
void**)&pi);
-
printf(
"子线程返回的数据是:%d\n",*pi);
-
return
0;
-
}
-
输出结果:
-
子线程返回的数据是:
5050
(5)示例解析
上例中主要需要注意的是 pthread_join 获取返回值。
从线程过程函数中返回值的方法:
线程过程函数将所需返回的内容放在一块内存中,返回该内存的地址,同时保证这块内存,在函数返回即线程结束以后依然有效。
若 retval 参数非 NULL,则 pthread_join 函数将线程过程函数所返回的指针,拷贝到该参数所指向的内存中。
若线程过程函数所返回的指针指向动态分配的内存,则还需保证在用过该内存之后释放它。
2、函数 pthread_detach
-
#include <pthread.h>
-
int pthread_detach(pthread_t thread);
-
返回值:成功返回
0;失败返回一个错误码
(1)函数功能
主要用于将参数指定的线程标记为分离状态,对于分离状态的线程来说:当该线程终止后,会自动将资源释放给系统,不需要其他线程的加入/等待,也就是说分离的线程无法被其他线程使用 pthread_join 进行等待。
建议:对于新启动的线程来说,要么使用 pthread_detach 设置为分离状态,要么使用 pthread_join 设置为可加状态。
(2)示例说明
-
#include <stdio.h>
-
#include <pthread.h>
-
-
void *task (void *p)
-
{
-
int i =
0;
-
for (i =
0; i <=
10; i++)
-
printf (
"子线程中:i = %d\n", i);
-
}
-
int main (void)
-
{
-
//启动一个子线程,打印1~10之间的数
-
pthread_t tid;
-
pthread_create (&tid,
NULL, task,
NULL);
-
//设置子线程为分离的状态
-
pthread_detach (tid);
-
//主线程进行等待,然后打印1~10之间的数
-
pthread_join (tid,
NULL);
-
int i =
0;
-
for (i =
1; i <=
10; i++)
-
printf (
"主线程中:i = %d\n", i);
-
return
0;
-
}
-
输出结果:
-
主线程中:i =
1
-
主线程中:i =
2
-
主线程中:i =
3
-
主线程中:i =
4
-
主线程中:i =
5
-
主线程中:i =
6
-
主线程中:i =
7
-
主线程中:i =
8
-
主线程中:i =
9
-
主线程中:i =
10
(3)示例解析
我们上面有讲到,如果使用 pthread_join 等待,则等待子线程完成后打印主线程1~10之间的数。
但是使用了 pthread_detach 分离,则直接打印主线程1~10之间的数,且 pthread_join 等待也会失效。
六、线程终止
如果进程中的任意线程调用了 exit、_Exit 或者 _exit,那么整个进程就会终止。与此相比类似,如果默认的动作是终止进程,那么发送到线程的信号就会终止整个进程。
单个线程可以通过 3 种方式退出,因此可以在不终止整个进程的情况下,停止它的控制流。
(1)线程可以简单地从启动例程中返回,返回值是线程的退出码。
(2)线程可以被同一进程中的其他线程取消。
(3)线程调用 pthread_exit。
1、我们讲一下第三种调用 pthread_exit。
-
#include <pthread.h>
-
void pthread_exit(void *retval);
(1)函数功能
主要用于终止正在运行的线程,通过参数 retval 来带出线程的退出状态信息。
在同一个进程中的其他线程可以通过调用 pthread_join 函数来获取退出状态信息。
(2)函数解析
在线程过程函数或者被线程过程函数直接或间接调用的函数中,调用 pthread_exit 函数,其效果都与在线程过程函数中执行 return 语句效果一样 -- 终止调用线程。
注意,在任何线程中调用 exit 函数,被终止的都是进程。当然随着进程的终止,隶属于该进程的包括调用线程在内的所有线程也都一并终止。
(3)示例说明
-
//示例一
-
#include <stdio.h>
-
#include <pthread.h>
-
#include <stdlib.h>
-
-
void *task (void *p)
-
{
-
int i =
0;
-
for (i =
1; i <
100; i++)
-
{
-
if (i ==
10)
-
{
-
//return (void*)i;
-
pthread_exit ((
void*)i);
-
//exit (100);
-
}
-
printf (
"子线程中:i = %d\n", i);
-
}
-
}
-
-
int main (void)
-
{
-
pthread_t tid;
-
pthread_create (&tid,
NULL, task,
NULL);
-
-
int res =
0;
-
pthread_join (tid, (
void**)&res);
-
printf (
"res = %d\n", res);
-
return
0;
-
}
-
编译:
# gcc test.c -lpthread
-
-
输出结果:
-
子线程中:i =
1
-
子线程中:i =
2
-
子线程中:i =
3
-
子线程中:i =
4
-
子线程中:i =
5
-
子线程中:i =
6
-
子线程中:i =
7
-
子线程中:i =
8
-
子线程中:i =
9
-
res =
10
-
//示例二
-
#include <stdio.h>
-
#include <pthread.h>
-
#include <stdlib.h>
-
#include <string.h>
-
#include <unistd.h>
-
-
void circle_area (double r)
-
{
-
double* s =
malloc (
sizeof (
double));
-
*s =
3.14 * r * r;
-
-
pthread_exit (s);
-
}
-
-
void* start_routine (void* arg)
-
{
-
circle_area (*(
double*)arg);
-
printf(
"此句将不被执行");
-
return
NULL;
-
}
-
-
int main()
-
{
-
double r =
10.0;
-
pthread_t thread;
-
int error = pthread_create (&thread,
NULL, start_routine, (
void*)&r);
-
if (error)
-
{
-
perror (
"pthread_create");
-
exit (EXIT_FAILURE);
-
}
-
-
double* s;
-
if (pthread_join (thread, (
void**)&s))
-
{
-
perror (
"pthread_join");
-
exit (EXIT_FAILURE);
-
}
-
-
printf (
"圆面积:%g\n", *s);
-
free (s);
-
-
return
0;
-
}
-
输出结果:
-
圆面积:
314
(4)示例解析
该示例说明了,在任何线程中调用 exit 函数,被终止的都是进程。进程终止可其他线程就就同样终止了。
使用 pthread_join 获取退出状态信息,return 可以返回退出状态信息,pthread_exit 同样也可以返回退出状态信息。
七、线程取消
1、函数 pthread_cancel
-
#include <pthread.h>
-
int pthread_cancel(pthread_t thread);
-
返回值:成功返回
0;失败返回错误码
(1)函数功能
主要用于对参数指定的线程发送取消的请求。
(2)函数解析
该函数只是向线程发出取消请求,并不等于线程终止。缺省情况下,线程在收到取消请求以后,并不会立即终止,而是仍继续运行,直到其达到某个取消点。在取消点处,线程检查其自身是否已被取消,若是则立即终止。
当线程调用一些特定函数时,取消点会出现。
2、函数 pthread_setcancelstate
-
#include <pthread.h>
-
int pthread_setcancelstate(int state, int *oldstate);
-
返回值:成功返回
0,失败返回错误码
(1)函数功能
主要用于设置新的取消状态,返回之前的取消状态。
(2)参数解析
state:取消状态,可取以下值
PTHREAD_CANCEL_ENABLE 接受取消请求(缺省)
PTHREAD_CANCEL_DISABLE 忽略取消请求
oldstate:输出原取消状态,可取 NULL
3、函数 pthread_setcanceltype
-
#include <pthread.h>
-
int pthread_setcanceltype(int type, int *oldtype);
-
返回值:成功返回
0,;失败返回错误码
(1)函数功能
主要用于设置新的取消类型,获取之前的取消类型
(2)参数解析
type:取消类型,可取以下值
PTHREAD_CANCEL_DEFERRED 延迟取消(缺省)
被取消线程接收到取消请求之后并不立即终止,而是一直等到执行了特定的函数(取消点)之后再终止。
PTHREAD_CANCEL_ASYNCHRONOUS 异步取消
被取消线程可以在任意时刻终止,而不是非得遇到取消点。
oldtype:输出原取消类型,可取 NULL
4、示例说明
-
//线程取消函数的使用
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <unistd.h>
-
#include <pthread.h>
-
-
void* task(void* p)
-
{
-
//设置允许被取消
-
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,
NULL);
-
//设置为立即取消
-
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,
NULL);
-
while(
1)
-
{
-
printf(
"I am superman!\n");
-
sleep(
1);
-
}
-
}
-
-
void* task2(void* p)
-
{
-
printf(
"开始取消线程...\n");
-
sleep(
5);
-
printf(
"取消线程结束\n");
-
pthread_cancel(*(
pthread_t*)p);
-
}
-
-
int main(void)
-
{
-
//1.启动一个新线程,不断进行打印
-
pthread_t tid;
-
pthread_create(&tid,
NULL,task,
NULL);
-
-
//2.启动另外一个新线程,负责取消上述线程
-
pthread_t tid2;
-
pthread_create(&tid2,
NULL,task2,&tid);
-
//3.主线程等待子线程的结束
-
-
pthread_join(tid,
NULL);
-
pthread_join(tid2,
NULL);
-
return
0;
-
}
-
-
编译:
# gcc test.c -lpthread
-
-
输出结果:
-
开始取消线程...
-
I am superman!
-
I am superman!
-
I am superman!
-
I am superman!
-
I am superman!
-
取消线程结束
5、示例解析
创建了两个线程,线程 2 负责取消线程 1。而设置取消状态为允许被取消,取消类型是立即取消。
八、线程清理处理程序
1、函数 pthread_cleanup_push、pthread_cleanup_pop
-
#include <pthread.h>
-
void pthread_cleanup_push(void (*routine)(void *), void *arg);
-
void pthread_cleanup_pop(int execute);
(1)函数解析
与进程在退出时可用 atexit 函数安排退出时类似的,线程也可以安排它退出时需要调用的函数。这样的函数就称为 线程清理处理程序。一个线程可以建立多个清理处理程序。处理程序记录在栈中,也就是说,它们的执行顺序与它们注册时相反。
(2)参数解析
当线程执行以下动作时,清理函数 routine 是由 pthread_cleanup_push 函数调度的,调用时只有一个参数 arg:
调用 pthread_exit 时;
响应取消请求时;
用非零 execute 参数设置 0,清理函数将不被调用。
不管发生上述哪种情况,pthread_cleanup_pop 都将删除 pthread_cleanup_push 调用建立的清理处理程序。
(3)示例说明
-
#include "apue.h"
-
#include <pthread.h>
-
-
void
-
cleanup
(void *arg)
-
{
-
printf(
"cleanup: %s\n", (
char *)arg);
-
}
-
-
void *
-
thr_fn1
(void *arg)
-
{
-
printf(
"thread 1 start\n");
-
pthread_cleanup_push(cleanup,
"thread 1 first handler");
-
pthread_cleanup_push(cleanup,
"thread 1 second handler");
-
printf(
"thread 1 push complete\n");
-
if (arg)
-
return((
void *)
1);
-
pthread_cleanup_pop(
0);
-
pthread_cleanup_pop(
0);
-
return((
void *)
1);
-
}
-
-
void *
-
thr_fn2
(void *arg)
-
{
-
printf(
"thread 2 start\n");
-
pthread_cleanup_push(cleanup,
"thread 2 first handler");
-
pthread_cleanup_push(cleanup,
"thread 2 second handler");
-
printf(
"thread 2 push complete\n");
-
if (arg)
-
pthread_exit((
void *)
2);
-
pthread_cleanup_pop(
0);
-
pthread_cleanup_pop(
0);
-
pthread_exit((
void *)
2);
-
}
-
-
int
-
main
(void)
-
{
-
int err;
-
pthread_t tid1, tid2;
-
void *tret;
-
-
err = pthread_create(&tid1,
NULL, thr_fn1, (
void *)
1);
-
if (err !=
0)
-
err_exit(err,
"can't create thread 1");
-
err = pthread_create(&tid2,
NULL, thr_fn2, (
void *)
1);
-
if (err !=
0)
-
err_exit(err,
"can't create thread 2");
-
err = pthread_join(tid1, &tret);
-
if (err !=
0)
-
err_exit(err,
"can't join with thread 1");
-
printf(
"thread 1 exit code %ld\n", (
long)tret);
-
err = pthread_join(tid2, &tret);
-
if (err !=
0)
-
err_exit(err,
"can't join with thread 2");
-
printf(
"thread 2 exit code %ld\n", (
long)tret);
-
exit(
0);
-
}
-
编译:
# gcc test.c -lpthread
-
-
输出结果:
-
thread
2 start
-
thread
2 push complete
-
cleanup: thread
2 second handler
-
cleanup: thread
2 first handler
-
thread
1 start
-
thread
1 push complete
-
thread
1
exit code
1
-
thread
2
exit code
2
(4)示例解析
两个线程都正确的启动和退出了,但是只有第二个线程的清理处理程序被调用了,而区别是一个是 return 返回,一个为调用 pthread_exit。因此,可以看出如果线程是通过从它的启动例程中返回而终止的话,它的清理处理程序就不会被调用。还要注意,清理处理程序时按照与它们安装时相反的顺序被调用的。- 点赞 2
- 收藏
- 分享