Linux线程

发布时间:2024年01月24日

目录

一、线程的创建与回收

二、线程的分离

三、线程的取消与清理


一、线程的创建与回收

#include  <pthread.h>
int  pthread_create(pthread_t *thread, const pthread_attr_t *attr, 
                    void *(*routine)(void *), void *arg);

 成功返回0,失败时返回错误码
 thread 线程对象
 attr 线程属性,NULL代表默认属性
 routine 线程执行的函数
 arg 传递给routine的参数 ,参数是void * ,注意传递参数格式,

传值:?

?ret = pthread_create(&pthread[i],NULL,(void*)testattr,(void*)i);

printf("arg = %d\n",(int)arg);

#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<stdlib.h>


void* testattr(void* arg)
{
	printf("child pthread\n");
	printf("arg = %d\n",(int)arg);
//	printf("arg = %d\n",*(int*)arg);
	return NULL;
}
int main(int argc,const char* argv[])
{
	pthread_t pthread[5];
	int ret = 0;
	int i = 0;
	for(i = 0;i<5;i++)
	{
		ret = pthread_create(&pthread[i],NULL,(void*)testattr,(void*)i);
	//	ret = pthread_create(&pthread[i],NULL,(void*)testattr,(void*)&i);
		printf("create\n");
	//	sleep(1);
		if(ret == EOF)
		{
			perror("pthread_create");
			exit(-1);
		}
	}
	printf("test\n");
	for(i = 0; i<5;i++)
	{
		printf("wait\n");
		pthread_join(pthread[i],NULL);
	}
	return 0;
}

?打印结果:

create
create
create
create
create
test
wait
child pthread
arg = 4
child pthread
arg = 3
child pthread
arg = 2
child pthread
arg = 1
child pthread
arg = 0
wait
wait
wait
wait

传址:加sleep(1)

linux@linux:~/code_Linux/code_2024_1_24/Linux_csdn$ vim pthread.c
linux@linux:~/code_Linux/code_2024_1_24/Linux_csdn$ gcc -o pthread pthread.c -lpthread
linux@linux:~/code_Linux/code_2024_1_24/Linux_csdn$ ./pthread
create
child pthread
arg = 0
create
child pthread
arg = 1
create
child pthread
arg = 2
create
child pthread
arg = 3
create
child pthread
arg = 4
test
wait
wait
wait
wait
wait

传址:不加sleep(1)

linux@linux:~/code_Linux/code_2024_1_24/Linux_csdn$ gcc -o pthread pthread.c -lpthread
linux@linux:~/code_Linux/code_2024_1_24/Linux_csdn$ ./pthread
create
create
create
create
create
test
wait
child pthread
arg = 0
child pthread
arg = 0
child pthread
arg = 0
child pthread
arg = 0
child pthread
arg = 0
wait
wait
wait
wait
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<stdlib.h>


void* testattr(void* arg)
{
	printf("child pthread\n");
//	printf("arg = %d\n",(int)arg);
	printf("arg = %d\n",*(int*)arg);
	return NULL;
}
int main(int argc,const char* argv[])
{
	pthread_t pthread[5];
	int ret = 0;
	int i = 0;
	for(i = 0;i<5;i++)
	{
//		ret = pthread_create(&pthread[i],NULL,(void*)testattr,(void*)i);
		ret = pthread_create(&pthread[i],NULL,(void*)testattr,(void*)&i);
		printf("create\n");
		sleep(1);
		if(ret == EOF)
		{
			perror("pthread_create");
			exit(-1);
		}
	}
	printf("test\n");
	for(i = 0; i<5;i++)
	{
		printf("wait\n");
		pthread_join(pthread[i],NULL);
	}
	return 0;
}
当使用 (void*)i 作为参数传递给 pthread_create 时
传递的是 i 的值的拷贝,而不是 i 的地址。
因此,每个线程得到的参数都是独立的,不会在后续循环迭代中受到影响。

而当用 (void*)&i 作为参数传递时,你传递的是 i 的地址,所有线程共享相同的地址。
因此,在后续循环迭代中,i 的值可能会改变,影响所有线程的参数。

当你使用 (void*)&i 并添加 sleep(1) 时,主线程被强制等待一秒钟,
这可能给其他线程足够的时间启动并复制当前的 i 值。
但这并不是一个稳定的解决方案,因为线程调度的行为可能会因系统和其他因素而异。

因此,使用 (void*)i 避免这个问题,因为每个线程都得到了 i 的独立拷贝,而不受主线程循环迭代的影响。
pthread_join 是一个用于等待指定线程结束的函数。
通过使用 pthread_join,主线程等待每个创建的子线程执行完毕,然后再继续执行。

上述代码,主线程会调用 pthread_join(pthread[i], NULL); 等待每个线程结束。
这样做的目的是确保主线程在退出之前等待每个子线程的完成。

这是因为主线程和子线程是并行执行的,如果主线程提前退出,那么所有尚未完成的子线程可能会被强制终止,导致未完全执行的线程。
pthread_join 提供了一种等待子线程完成的机制,确保主线程在所有子线程完成后再退出。

?线程的回收:

使用pthread_join 函数:
#include  <pthread.h>
 int  pthread_join(pthread_t thread, void **retval);

注意:pthread_join 是阻塞函数,如果回收的线程没有结束,则一直等待
linux@linux:~/code_Linux/code_2024_1_24/Linux_csdn$ ./pthread_joinpthread = 3075496768
arg = 5
retval = hello,world
linux@linux:~/code_Linux/code_2024_1_24/Linux_csdn$ cat pthread_join.c
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<stdlib.h>

void* testattr(void* arg)
{
	printf("pthread = %lu\n",(unsigned long)pthread_self());
	printf("arg = %d\n",*(int*)arg);
	pthread_exit("hello,world");
}
int main(int argc,const char* argv[])
{
	pthread_t pthread;
	int arg = 5;
	pthread_create(&pthread,NULL,(void*)testattr,&arg);
	void* retval;
	pthread_join(pthread,&retval);
	printf("retval = %s\n",(char*)retval);
	exit(0);
}

二、线程的分离

两种方式:

1 使用pthread_detach

linux@linux:~/code_Linux/code_2024_1_24/Linux_csdn$ ./pthread_detach
This is child pthread
This is child pthread
This is child pthread
This is child pthread
This is child pthread
^C
linux@linux:~/code_Linux/code_2024_1_24/Linux_csdn$ cat  pthread_detach.c#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
void* testattr(void* arg)
{

	pthread_detach(pthread_self());
	printf("This is child pthread\n");
	sleep(5);
	pthread_exit("hello,world");
}
int main(int argc,const char* argv[])
{
	pthread_t pthread;
	int i = 0;
	for(i = 0;i<5;i++)
	{
		pthread_create(&pthread,NULL,testattr,NULL);
	}
	while(1)
	{
		sleep(1);
	}

	return 0;
}

2 创建线程时候设置为分离属性

? pthread_attr_t attr;

? pthread_attr_init(&attr);

? pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);

三、线程的取消与清理

线程的取消:?

意义:随时杀掉一个线程
int pthread_cancel(pthread_t thread);

注意:线程的取消要有取消点才可以,不是说取消就取消,线程的取消点主要是阻塞的系统调用
如果没有取消点,手动设置一个
void pthread_testcancel(void);

设置取消使能或禁止
int pthread_setcancelstate(int state, int *oldstate);
PTHREAD_CANCEL_ENABLE
PTHREAD_CANCEL_DISABLE

设置取消类型

int pthread_setcanceltype(int type, int *oldtype);
PTHREAD_CANCEL_DEFERRED                等到取消点才取消
PTHREAD_CANCEL_ASYNCHRONOUS           目标线程会立即取消
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void *func(void *arg){
    printf("This is child thread\n");
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);    
//    while(1)
    {
        sleep(5);
        pthread_testcancel();
    }
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
    while(1){
        sleep(1);
    }
 
 
    pthread_exit("thread return");
}
 
 
int main(){
    pthread_t tid;
    void *retv;
    int i;
    pthread_create(&tid,NULL,func,NULL);
    sleep(1);
    pthread_cancel(tid);
    pthread_join(tid,&retv);
//    printf("thread ret=%s\n",(char*)retv);
    while(1){    
        sleep(1);
    } 
 
}
pthread_testcancel 的作用是检查是否有取消请求在当前线程中排队等待执行。
取消请求通常是通过调用 pthread_cancel 函数发起的,用于请求取消线程的执行。

在多线程环境中,可以使用 pthread_testcancel 来主动检查是否有取消请求,并在适当的地方进行线程取消。
这对于长时间运行的线程中插入取消点(cancellation points)是有用的,以确保线程可以在需要时及时取消。

?示例代码:

#include <pthread.h>
#include <stdio.h>

void* myThreadFunction(void* arg) {
    for (int i = 0; i < 10; ++i) {
        // Do some work
        printf("Working...\n");

        // Check for cancellation request
        pthread_testcancel();

        // More work
    }
    return NULL;
}

int main() {
    pthread_t myThread;
    pthread_create(&myThread, NULL, myThreadFunction, NULL);

    // Allow some time for the thread to execute
    sleep(2);

    // Cancel the thread
    pthread_cancel(myThread);

    // Wait for the thread to finish
    pthread_join(myThread, NULL);

    printf("Main thread finished.\n");
    return 0;
}

?????????在上面的例子中,pthread_testcancel 在循环中被调用,以检查是否有取消请求。如果有取消请求,线程会在这个点被取消。在主线程中,pthread_cancel 被用于请求取消 myThread 线程的执行。请注意,使用线程取消时需要小心,确保在适当的地方插入取消点,以避免资源泄漏或不一致的状态。

线程的清理:

必要性: 当线程非正常终止,需要清理一些资源。

void pthread_cleanup_push(void (*routine) (void *), void *arg)
void pthread_cleanup_pop(int execute)

  1. pthread_cleanup_push 函数:

    • 用于将清理处理例程(cleanup routine)压入线程的清理处理栈。
    • 在线程创建时,通常会使用 pthread_cleanup_push 来指定在线程取消(被终止)时需要执行的清理处理函数。
    • 语法:void pthread_cleanup_push(void (*routine)(void*), void *arg);
  2. pthread_cleanup_pop 函数:

    • 用于弹出清理处理栈顶部的清理处理例程。
    • 通常与 pthread_cleanup_push 配套使用,指定了清理处理函数后,需要使用 pthread_cleanup_pop 来相应地移除它。
    • 语法:void pthread_cleanup_pop(int execute);execute 参数:如果 execute 的值为非零,则执行栈顶的清理处理例程,如果为零,则不执行。这样,你可以在取消点选择是否执行清理处理。

示例代码:?

#include <stdio.h>
#include <pthread.h>

void cleanup_handler(void *arg) {
    printf("Cleanup: %s\n", (const char*)arg);
}

void* myThreadFunction(void* arg) {
    // Push cleanup handler onto the cleanup stack
    pthread_cleanup_push(cleanup_handler, "Cleanup routine 1");

    printf("Thread is running...\n");

    // Simulate some work
    for (int i = 0; i < 3; ++i) {
        sleep(1);
        printf("Working...\n");
    }

    // Pop the cleanup handler (execute it)
    pthread_cleanup_pop(1);

    return NULL;
}

int main() {
    pthread_t myThread;
    pthread_create(&myThread, NULL, myThreadFunction, NULL);
    
    // Allow some time for the thread to execute
    sleep(2);

    // Cancel the thread
    pthread_cancel(myThread);

    // Wait for the thread to finish
    pthread_join(myThread, NULL);

    printf("Main thread finished.\n");
    return 0;
}

????????在上述例子中,pthread_cleanup_push 用于将 cleanup_handler 函数压入清理处理栈,然后在线程的执行过程中,pthread_cleanup_pop 用于弹出并执行清理处理。这样,在线程被取消时,cleanup_handler 将被执行,实现了清理处理的功能。?

routine 函数被执行的条件:
1.?? ?被pthread_cancel取消掉。

#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
void cleanup(void* argc)
{
	printf("cleanup,argc = %s\n",(char*)argc);
}
void* testattr(void* argc)
{
	printf("child pthread\n");
//	pthread_setcancelstate(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);
	pthread_cleanup_push(cleanup,"abcd");
	while(1)
	{
		sleep(1);//创造取消点
	}
	//pthread_testcancel();
	pthread_cleanup_pop(1);//正数进行释放
	pthread_exit("hello,world");
}
int main(int argc,const char* argv[])
{
	pthread_t pthread;
	pthread_create(&pthread,NULL,testattr,NULL);
	void* retv;
	sleep(1);//足够的时间去创建线程
	pthread_cancel(pthread);//杀死线程
	pthread_join(pthread,&retv);//回收线程
	//printf("%s\n",(char*)retv);
	while(1)
	{
		sleep(1);
	}
	exit(0);
}

??pthread_cancel 不会立即终止线程,而是在等待线程到达取消点时终止。在testattr 函数中,sleep(1) 提供了一个取消点(cancellation point),使得线程在这里可以被取消。????????? ? ? ? ??????????pthread_cancel 函数用于请求取消线程的执行,但线程只有在达到取消点时才会被实际取消。取消点是线程可以被取消的一些特定位置,确保在这些位置上线程的状态是一致的。

sleep 函数是标准的取消点,它允许线程进入睡眠状态并等待一段时间。
当线程在 sleep 中时,它是可以被取消的。

如果你去掉 sleep(1),那么 while(1) 循环中就不再有明显的取消点。
取消点的存在是为了确保在某些位置上线程的状态是一致的,并且允许取消操作发生。
如果你的循环中没有明显的取消点,那么线程在执行过程中可能会在任意位置被取消,这可能导致一些不一致的状态。

在没有取消点的情况下,线程可能会在不确定的位置被取消,可能导致资源泄漏或其他问题。
因此,为了确保线程在合适的时候被取消,通常会在代码中插入明确的取消点。
这就是为什么在上述代码中使用了 sleep(1) 来创建一个取消点,以确保在 sleep 的时候线程是可以被取消的。
一些标准的库函数,例如 sleep、pthread_join、pthread_testcancel,都是取消点。
这意味着当线程执行这些函数时,系统会检查是否有取消请求。

2.?? ?执行pthread_exit?

linux@linux:~/code_Linux$ ./test
child pthread
cleanup,argc = abcd
^C   
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
void cleanup(void* argc)
{
	printf("cleanup,argc = %s\n",(char*)argc);
}
void* testattr(void* argc)
{
	printf("child pthread\n");

	pthread_cleanup_push(cleanup,"abcd");


	pthread_exit("thread pop\n");
	pthread_cleanup_pop(0);

}
int main(int argc,const char* argv[])
{
	pthread_t pthread;
	pthread_create(&pthread,NULL,testattr,NULL);
	void* retv;
	sleep(1);//足够的时间去创建线程

	pthread_join(pthread,&retv);//回收线程

	while(1)
	{
		sleep(1);
	}
	exit(0);
}

?

现在我们将exit放在后面去:

? ? ? ? pthread_cleanup_pop(0);

????????pthread_exit("thread pop\n");

运行结果为:

从上可以看出child pthread虽然成功退出了,但是

void cleanup(void* argc)
{
?? ?printf("cleanup,argc = %s\n",(char*)argc);
}

并没有正常执行,因为没有遇到pthread_exit();只需要进行修改,把pop的参数修改为非0即可打印出我们想要的结果,如下:

3.?? ?非0参数执行pthread_cleanup_pop()

注意:
1.??必须成对使用,即使pthread_cleanup_pop不会被执行到也必须写上,否则编译错误。

2.pthread_cleanup_pop()被执行且参数为0,pthread_cleanup_push回调函数routine不会被执行。

3.thread_cleanup_push 和pthread_cleanup_pop可以写多对,routine执行顺序正好相反。

linux@linux:~/code_Linux$ vim test.c
linux@linux:~/code_Linux$ gcc -o test test.c -lpthread
linux@linux:~/code_Linux$ ./test
child pthread
cleanup,argc = haha
cleanup,argc = abcd
^C

?先打印haha,再打印的abcd

#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
void cleanup(void* argc)
{
	printf("cleanup,argc = %s\n",(char*)argc);
}
void cleanup1(void* argc)
{
	printf("cleanup,argc = %s\n",(char*)argc);
}
void* testattr(void* argc)
{
	printf("child pthread\n");
//	pthread_setcancelstate(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);
	pthread_cleanup_push(cleanup,"abcd");
	pthread_cleanup_push(cleanup1,"haha");
//	while(1)
//	{
		sleep(1);//创造取消
	//pthread_testcancel();
//	pthread_exit("thread pop\n");
	pthread_cleanup_pop(1);
	pthread_cleanup_pop(1);
	pthread_exit("hello,world");
}
int main(int argc,const char* argv[])
{
	pthread_t pthread;
	pthread_create(&pthread,NULL,testattr,NULL);
	void* retv;
	sleep(1);//足够的时间去创建线程
//	pthread_cancel(pthread);//杀死线程
	pthread_join(pthread,&retv);//回收线程
	//printf("%s\n",(char*)retv);
	while(1)
	{
		sleep(1);
	}
	exit(0);
}

4.?线程内的return 可以结束线程,也可以给pthread_join返回值,但不能触发pthread_cleanup_push里面的回调函数,所以我们结束线程尽量使用pthread_exit退出线程。

?

void* testattr(void* argc)
{
	printf("child pthread\n");
	pthread_cleanup_push(cleanup,"abcd");
	pthread_cleanup_push(cleanup1,"haha");
	pthread_cancel(pthread_self());
	printf("test pthread_self\n");
	pthread_cleanup_pop(1);
	pthread_cleanup_pop(1);
	pthread_exit("hello,world");
}

分明已经取消了,为什么还会打印test,是因为没有取消点。?

linux@linux:~/code_Linux$ gcc -o test test.c -lpthread
linux@linux:~/code_Linux$ ./test
child pthread
test pthread_self
cleanup,argc = haha
cleanup,argc = abcd

加入后打印没有变化,是因为默认是在取消点处取消

?可以设置立即取消。?

pthread_setcancelstate(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);

?最后注意:return线程内的return 可以结束线程,也可以给pthread_join返回值,但不能触发

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
 
void cleanup(void *arg)
{
    printf("cleanup,arg=%s\n",(char*)arg);
 
}
void cleanup2(void* arg)
{
 
    printf("cleanup2,arg=%s\n",(char*)arg);
}
 
void *func(void *arg)
{
    printf("This is child thread\n");
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);
    pthread_cleanup_push(cleanup,"abcd");
    pthread_cleanup_push(cleanup2,"efgh");
    {
        sleep(1);
         
    }
    return "1234";
 
    while(1)
    {
        printf("sleep\n");
        sleep(1);
    }
    pthread_exit("thread return");
    pthread_cleanup_pop(1);
    pthread_cleanup_pop(1);
    sleep(10);
    pthread_exit("thread return");
}
 
 
int main()
{
    pthread_t tid;
    void *retv;
    int i;
    pthread_create(&tid,NULL,func,NULL);
    sleep(1);

    pthread_join(tid,&retv);
    printf("thread ret=%s\n",(char*)retv);
    while(1)
    {    
        sleep(1);
    } 
 
}

文章来源:https://blog.csdn.net/weixin_57604904/article/details/135791223
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。