一、线程分离
线程分离是指将一个线程从主线程中分离出来,使其能够独立运行,并且不再需要其他线程使用pthread_join()
函数来等待其结束。
当一个线程被设置为分离状态后,它结束时,系统会自动回收其资源,而不需要其他线程进行显式的资源回收操作。
线程在Linux系统中有两种状态:结合态(joinable)和分离态(detached)。默认情况下,线程被创建为结合态。
- 在结合态下,当线程函数执行完毕或调用
pthread_exit
退出时,线程所占用的堆栈和线程描述符等资源不会立即释放。只有当其他线程调用pthread_join
函数等待该线程结束时,这些资源才会被释放。这种状态适用于需要等待线程完成并获取其返回值的情况。
- 而在分离态下,当线程函数执行完毕或调用
pthread_exit
退出时,线程所占用的资源会自动释放,无需其他线程进行显式的资源回收操作。要将线程设置为分离态,可以使用pthread_detach
函数。这种状态适用于不需要等待线程完成或获取其返回值的情况,可以提高程序的效率和资源利用率。
创建一个线程后应回收其资源,但使用pthread_join函数会使调用者阻塞,故Linux提供了 线程分离函数:pthread_detach
#include <pthread.h>
int pthread_detach(pthread_t thread);
功能:
使调用线程与当前进程分离,使其成为一个独立的线程,
该线程终止时,系统将自动回收它的资源。
参数:
thread:指定的子线程的id
返回值:
成功:0
失败:非0
案例
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *thread_fun(void *arg)
{
printf("子线程正在运行\n");
sleep(3);
printf("子线程要退出了\n");
}
int main(int argc, char const *argv[])
{
printf("主控线程正在执行\n");
pthread_t thread;
if(pthread_create(&thread, NULL, thread_fun, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
//通过pthread_detach函数将子线程设置为分离态,既不用阻塞,也可以自动回收子线程退出的资源
if(pthread_detach(thread) != 0)
{
perror("fail to pthread_detach");
exit(1);
}
#if 0
//如果原本子线程是结合态,需要通过pthrad_join函数回收子线程退出的资源,
//但是这个函数是一个阻塞函数,如果子线程不退出,就会导致当前进程(主控线程)无法继续执行,大 大的限制了代码的运行效率
//如果子线程已经设置为分离态,就不需要再使用pthread_join了
if(pthread_join(thread, NULL) != 0)
{
perror("fail to pthread_join");
exit(1);
}
#endif
while(1)
{
printf("hello world\n");
sleep(1);
}
return 0;
}
二、线程退出
1、线程退出函数
#include <pthread.h>
void pthread_exit(void *retval);
功能:
退出正在执行的线程
参数:
retval:当前线程的退出状态值,
这个值可以被调用pthread_join函数的线程接收到
返回值:
无
注: 一个进程中的多个线程是共享该进程的数据段,因此,通常线程退出后所占用的资源并 不会释放。如果要释放资源,结合态需要通过pthread_join函数,分离态则自动释放
案例:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *thread_fun(void *arg)
{
printf("子线程正在运行\n");
static char buf[] = "This thread has quited";
int i;
for(int i = 0; i < 10; i++)
{
if(i == 5)
{
//通过pthread_exit函数退出当前线程
//pthread_exit(NULL);
pthread_exit(buf);
}
printf("*******************\n");
sleep(1);
}
}
int main(int argc, char const *argv[])
{
printf("主控线程正在执行\n");
pthread_t thread;
if(pthread_create(&thread, NULL, thread_fun, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
//pthread_join(thread, NULL);
char *str;
pthread_join(thread, (void **)&str);
printf("str = %s\n", str);
printf("进程要退出了\n");
return 0;
}
- 在这个代码中,子线程在循环到第5次时,调用了
pthread_exit
函数来退出线程,并将一个字符串指针作为返回值传递给主线程。在主线程中,通过pthread_join
函数来等待子线程的结束,并将子线程的返回值存储在指针str
中。最后,主线程打印出这个字符串,并退出整个进程。需要注意的是,在使用pthread_join
函数时,需要将第二个参数的类型转换为void **
,以正确地获取子线程的返回值。
执行结果
2.线程的取消
2.1 pthread_cancel()
#include <pthread.h>
int pthread_cancel(pthread_t thread);
功能:
取消线程
参数:
thread:要销毁的线程的id
返回值:
成功:0
失败:非0
pthread_cancel函数的实质是发信号给目标线程thread,使目标线程退出。
此函数只是发送终止信号给目标线程,不会等待取消目标线程执行完才返回。 然而发送成功并不意味着目标线程一定就会终止,线程被取消时,线程的取消属性会决 定线程能否被取消以及何时被取消。
线程的取消状态
即线程能不能被取消
线程取消点
即线程被取消的地方
线程的取消类型
在线程能被取消的状态下,是立马被取消结束还是执行到取消点的时候被取消结束
案例:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *pthread_fun(void *arg)
{
while(1)
{
printf("子线程正在运行\n");
sleep(1);
}
}
int main(int argc, char const *argv[])
{
pthread_t thread;
if(pthread_create(&thread, NULL, pthread_fun, NULL) != 0)
{
perror("fail to pthread_create");
}
// 等待3秒后,通过调用pthread_cancel函数取消子线程
sleep(3);
pthread_cancel(thread);
// 使用pthread_join函数等待子线程结束,并回收资源
pthread_join(thread, NULL);
return 0;
}
执行结果
2.2pthread_setcancelstate()
线程的取消状态
在Linux系统下,线程默认可以被取消。编程时可以通过pthread_setcancelstate函数 设置线程是否可以被取消。
#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
功能:
设置线程是否被取消
参数:
state:新的状态
PTHREAD_CANCEL_DISABLE:不可以被取消
PTHREAD_CANCEL_ENABLE:可以被取消
oldstate:保存调用线程原来的可取消状态的内存地址
返回值:
成功:0
失败:非0
案例:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *pthread_fun(void *arg)
{
// 通过pthread_setcancelstate设置取消的状态
// 设置为可以取消
// pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
// 设置为不可取消
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
while(1)
{
printf("子线程正在运行\n");
sleep(1);
}
}
int main(int argc, char const *argv[])
{
pthread_t thread;
if(pthread_create(&thread, NULL, pthread_fun, NULL) != 0)
{
perror("fail to pthread_create");
}
sleep(3);
// 调用pthread_cancel函数取消子线程
pthread_cancel(thread);
// 使用pthread_join函数等待子线程结束并回收资源
pthread_join(thread, NULL);
return 0;
}
执行结果
2.3 pthread_setcanceltype()
线程的取消类型
线程被取消后,该线程并不是马上终止,默认情况下线程执行到消点时才能被终止。
编 程时可以通过pthread_setcanceltype函数设置线程是否可以立即被取消。
#include <pthread.h>
int pthread_setcanceltype(int type, int *oldtype);
功能:
设置线程是否可以立即被取消
参数:
type:类型
PTHREAD_CANCEL_ASYNCHRONOUS:立即取消、
PTHREAD_CANCEL_DEFERRED:不立即被取消
oldtype:保存调用线程原来的可取消类型的内存地址
返回值:
成功:0
失败:非0
案例:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *pthread_fun(void *arg)
{
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
// 设置线程取消的类型
// 设置为立即取消
// pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
// 设置为不立即取消
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
while(1)
{
printf("子线程正在运行\n");
sleep(1);
}
}
int main(int argc, char const *argv[])
{
pthread_t thread;
if(pthread_create(&thread, NULL, pthread_fun, NULL) != 0)
{
perror("fail to pthread_create");
}
sleep(3);
// 调用pthread_cancel函数取消子线程
pthread_cancel(thread);
// 使用pthread_join函数等待子线程结束并回收资源
pthread_join(thread, NULL);
return 0;
}
- 这个代码中,子线程通过调用
pthread_setcancelstate
函数将自身的取消状态设置为PTHREAD_CANCEL_ENABLE
,即可以取消状态。然后,子线程通过调用pthread_setcanceltype
函数将自身的取消类型设置为PTHREAD_CANCEL_DEFERRED
,即延迟取消类型。这意味着子线程只有在到达取消点时才会响应取消请求。- 在主线程中,我们等待3秒后调用
pthread_cancel
函数来尝试取消子线程。由于子线程的取消类型是延迟取消,所以取消请求不会立即生效,而是在子线程到达下一个取消点时才会被处理。最后,我们使用pthread_join
函数等待子线程结束并回收资源。
执行结果
3、线程退出清理函数
和进程的退出清理一样,线程也可以注册它退出时要调用的函数,这样的函数称为线程 清理处理程序(thread cleanup handler)。
注意:
线程可以建立多个清理处理程序。
处理程序在栈中,故它们的执行顺序与它们注册时的顺序相反。
当线程执行以下动作时会调用清理函数:
1、调用pthread_exit退出线程。
2、响应其它线程的取消请求。
3、用非零execute调用pthread_cleanup_pop。
3.1 pthread_cleanup_push()
#include <pthread.h>
void pthread_cleanup_push(void (* routine)(void *), void *arg);
功能:
将清除函数压栈。即注册清理函数。
参数:
routine:线程清理函数的指针。
arg:传给线程清理函数的参数。
返回值:无
3.2 pthread_cleanup_pop()
#include <pthread.h>
void pthread_cleanup_pop(int execute);
功能:
将清除函数弹栈,即删除清理函数。
参数:
execute:线程清理函数执行标志位。
非0,弹出清理函数,执行清理函数。
0,弹出清理函数,不执行清理函数。
返回值:
无
3.3案例:验证线程调用pthread_exit函数时,系统自动调用线程清理函数
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
void mycleanup(void *arg)
{
printf("clean up ptr = %s\n", (char *)arg);
free((char *)arg);
}
void *thread(void *arg)
{
/*建立线程清理程序*/
printf("this is new thread\n");
char *ptr = NULL;
ptr = (char*)malloc(100);
pthread_cleanup_push(mycleanup, (void*)(ptr));
bzero(ptr, 100);
strcpy(ptr, "memory from malloc");
sleep(3);
printf("before exit\n");
pthread_exit(NULL);
/*注意push与pop必须配对使用,即使pop执行不到*/
printf("before pop\n");
pthread_cleanup_pop(1);
}
int main(int argc, char *argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, thread, NULL); // 创建一个线程
pthread_join(tid,NULL);
printf("process is dying\n");
return 0;
}
在这个代码中,我们定义了一个名为mycleanup
的函数,它接受一个指针参数并打印出该指针指向的字符串,然后使用free
函数释放该指针指向的内存。
在thread
函数中,我们首先打印一条消息以指示新线程的开始。然后,我们使用malloc
函数分配一块100字节的内存,并将指向该内存的指针存储在ptr
变量中。接下来,我们使用pthread_cleanup_push
函数将mycleanup
函数注册为线程的清理程序,并将ptr
指针作为参数传递给它。这意味着当线程退出时,mycleanup
函数将被自动调用,以释放ptr
指针指向的内存。
接下来,我们使用bzero
函数将分配的内存初始化为零,并使用strcpy
函数将字符串"memory from malloc"复制到该内存中。然后,我们使用sleep
函数使线程睡眠3秒,并打印一条消息以指示线程即将退出。
在线程退出之前,我们调用pthread_exit
函数以正常方式退出线程。这将导致已注册的清理程序mycleanup
被自动调用,以释放之前分配的内存。
在主函数中,我们使用pthread_create
函数创建一个新线程,并将thread
函数作为线程的入口点。然后,我们使用pthread_join
函数等待线程完成,并打印一条消息以指示进程即将退出。
执行结果
3.4案例:验证调用pthread_cleanup_pop函数时,系统自动调用线程清理函数
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
void cleanup_func1(void *arg)
{
printf("in cleanup func1\n");
printf("clean up ptr = %s\n", (char *)arg);
free((char *)arg);
}
void cleanup_func2(void *arg)
{
printf("in cleanup func2\n");
}
void *thread(void *arg)
{
char *ptr = NULL;
/*建立线程清理程序*/
printf("this is new thread\n");
ptr = (char*)malloc(100);
pthread_cleanup_push(cleanup_func1, (void*)(ptr));
pthread_cleanup_push(cleanup_func2, NULL);
bzero(ptr, 100);
strcpy(ptr, "memory from malloc");
/*注意push与pop必须配对使用,即使pop执行不到*/
sleep(3);
printf("before pop\n");
pthread_cleanup_pop(1); // 执行cleanup_func2
printf("before pop\n");
pthread_cleanup_pop(1); // 执行cleanup_func1
return NULL;
}
int main(int argc, char *argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, thread, NULL); // 创建一个线程
pthread_join(tid,NULL);
printf("process is dying\n");
return 0;
}
在这个代码中,我们定义了两个清理函数:cleanup_func1
和cleanup_func2
。cleanup_func1
接受一个指针参数,打印出该指针指向的字符串,并使用free
函数释放该指针指向的内存。cleanup_func2
则只打印一条消息。
在thread
函数中,我们首先打印一条消息以指示新线程的开始。然后,我们使用malloc
函数分配一块100字节的内存,并将指向该内存的指针存储在ptr
变量中。接下来,我们使用pthread_cleanup_push
函数将cleanup_func1
和cleanup_func2
注册为线程的清理程序。我们将ptr
指针作为参数传递给cleanup_func1
,并将NULL
作为参数传递给cleanup_func2
。这意味着当线程退出时,这两个函数将按照与它们被注册的相反顺序被自动调用。
接下来,我们使用bzero
函数将分配的内存初始化为零,并使用strcpy
函数将字符串"memory from malloc"复制到该内存中。然后,我们使用sleep
函数使线程睡眠3秒,并打印两条消息。
在线程退出之前,我们使用pthread_cleanup_pop
函数以与它们被注册的相反顺序弹出已注册的清理程序。第一个pthread_cleanup_pop
函数将导致cleanup_func2
被调用,而第二个pthread_cleanup_pop
函数将导致cleanup_func1
被调用。这将释放之前分配的内存,并打印相应的消息。
在主函数中,我们使用pthread_create
函数创建一个新线程,并将thread
函数作为线程的入口点。然后,我们使用pthread_join
函数等待线程完成,并打印一条消息以指示进程即将退出。
执行结果