UNIX再学习 -- 线程

终于要讲到线程部分,线程和进程让人够头痛的内容。

一、线程概念

老样子,我们还是按我们讲进程时的方式说起,参看:UNIX再学习 -- 进程环境
首先需要了解下,什么是线程。
Linux 下的线程,可能我们比较陌生,但是我们一直在玩 Windows 系统。应用程序文件、任务管理器,这些东西应该是很溜的。比如:


查看详细信息,最上一列,右击选择列;找到线程打对勾。如下图,就可看到线程数了。





通过上图我们可以看到 一个进程中有多个线程。但是到底什么是线程呢?
参看:进程与线程的一个简单解释  
大神举得例子很有意思。
进程好似车间,线程好似车间工人;任务是有很多工人协同完成;车间内的空间设施工人都是共享的;有些房间比如厕所一次只能容纳一人,进入需加锁(互斥锁)。

进程,是资源分配单位。线程,是CPU调度基本单位。
线程就是程序的执行路线,即进程内部的控制序列,或者说是进程的子任务。
一个进程可以同时拥有多个线程,即同时被系统调度的多条执行路线,但至少要有一个主线程。


一个进程的所有线程都共享进程的代码区、数据区、堆区(注意没有栈区)、环境变量和命令行参数、文件描述符、信号处理函数、当前目录、用户 ID 和组 ID 等。
一个进程的每个线程都拥有独立的 ID、寄存器值、栈内存、调度策略和优先级、信号掩码、errno变量以及线程私有数据等。
也可以说线程是包含在进程中的一种实体。它有自己的运行线索,可完成特定任务。可与其他线程共享进程中的共享变量及部分环境。可通过相互之间协同来完成进程所要完成的任务。
之前有转载一篇文章,可当扩展来看,参看:进程与线程及其区别

1.进程和线程的区别
什么是进程(Process):普通的解释就是,进程是程序的一次执行,而什么是线程(Thread),线程可以理解为进程中的执行的一段程序片段。在一个多任务环境中下面的概念可以帮助我们理解两者间的差别:进程间是独立的,这表现在内存空间,上下文环境;线程运行在进程空间内。一般来讲(不使用特殊技术)进程是无法突破进程边界存取其他进程内的存储空间;而线程由于处于进程空间内,所以同一进程所产生的线程共享同一内存空间。 同一进程中的两段代码不能够同时执行,除非引入线程。线程是属于进程的,当进程退出时该进程所产生的线程都会被强制退出并清除。线程占用的资源要少于进程所占用的资源。 进程和线程都可以有优先级。在线程系统中进程也是一个线程。可以将进程理解为一个程序的第一个线程。
线程是指进程内的一个执行单元,也是进程内的可调度实体.与进程的区别:
(1)地址空间:进程内的一个执行单元;进程至少有一个线程;它们共享进程的
地址空间;而进程有自己独立的地址空间;
(2)进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
(3)线程是处理器调度的基本单位,但进程不是.
(4)二者均可并发执行.

1.进程和线程的差别。
答:线程是指进程内的一个执行单元,也是进程内的可调度实体.
与进程的区别:
(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*。启动线程过程其实就是调用一个函数,只不过是在一个独立的线程中调用,函数一旦返回,线程即告结束。
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。因此,可以看出如果线程是通过从它的启动例程中返回而终止的话,它的清理处理程序就不会被调用。还要注意,清理处理程序时按照与它们安装时相反的顺序被调用的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

聚优致成

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值