概述
线程是轻量级的进程(LWP:light weight process),在 Linux 环境下线程的本质仍是进程。在计算机上运行的程序是一组指令及指令参数的组合,指令按照既定的逻辑控制计算机运行。操作系统会以进程为单位,分配系统资源,可以这样理解,进程是资源分配的最小单位,线程是操作系统调度执行的最小单位。
在Linux系统下,线程的创建和管理是通过pthread库实现的。pthread是POSIX线程库,提供了创建、终止、同步和通信线程的函数和数据结构。
创建线程
线程函数
每一个线程都有一个唯一的线程 ID,ID 类型为 pthread_t,这个 ID 是一个无符号长整形数,如果想要得到当前线程的线程 ID,可以调用如下函数:
pthread_t pthread_self(void); // 返回当前线程的线程ID
在Linux系统下,可以使用pthread_create函数来创建线程。pthread_create函数的原型如下:
#include <pthread.h>
int pthread_create(pthread_t* thread, const pthread_attr_t* attr, void* (*start_routine)(void*), void* arg);
- thread:指向pthread_t类型的指针,用于存储新创建线程的标识符。在成功创建线程后,该指针将被填充为一个唯一的标识符,用于后续对线程的引用。
- attr:指向pthread_attr_t类型的指针,用于指定线程的属性。线程属性对象可以控制线程的各种行为,例如线程的调度策略、栈大小、分离状态等。如果不需要对线程属性进行特殊设置,可以传入NULL,使用默认属性。
- start_routine:指向线程函数的指针,该函数是线程的入口点。线程函数是线程的实际执行体,当线程被创建时,将从该函数开始执行。函数的返回类型必须为void*,并接受一个void*类型的参数。线程函数可以执行任意操作,包括计算、访问共享资源等。
- arg:传递给线程函数的参数,类型为void*。可以将任意类型的数据传递给线程函数,只需将其转换为void*类型。在线程函数内部,可以使用适当的类型转换将参数恢复为原始类型。
返回值:线程创建成功返回 0,创建失败返回对应的错误号
在编写多线程程序的时候,如果想要让线程退出,但是不会导致虚拟地址空间的释放(针对于主线程),我们就可以调用线程库中的线程退出函数,只要调用该函数当前线程就马上退出了,并且不会影响到其他线程的正常运行,不管是在子线程或者主线程中都可以使用。
void pthread_exit(void *retval);
参数 retval 是一个指向任意类型的指针,表示线程的退出状态。线程的退出状态可以用于与其他线程进行通信或传递结果。
pthread_self() 是一个 POSIX 线程库函数,用于获取当前线程的线程 ID。
pthread_t pthread_self(void);
线程创建
注意:线程函数可以接受一个指向任意类型的参数,并且返回一个指向任意类型的指针。
首先使用vim pthread_create.c
创建c语言文件。
键入代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
void *callback(void* arg)
{
for(int i=0;i<5;++i)
{
printf("子线程:i=%d\n",i);
}
printf("子线程:%ld\n",pthread_self());
return NULL;
}
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,callback,NULL);
for(int i=0;i<5;++i)
{
printf("主线程:i=%d\n",i);
}
printf("主线程:%ld\n",pthread_self());
pthread_exit(NULL);
return 0;
}
保存文件后使用g++ pthread_create.c -lpthread -o app
命令进行编译。
该命令用于编译名为 pthread_create.c 的源代码文件,并链接 pthread 库生成可执行文件 app。
g++:是 GNU 编译器集合中的 C++ 编译器。
pthread_create.c:是要编译的源代码文件的名称。
-lpthread:表示链接 pthread 库,这是 POSIX 线程库。
-o app2:指定生成的可执行文件的名称为 app2
执行./app
,结果如下所示
线程回收
在线程编程中,线程回收是指等待线程执行结束并收回相关资源。在 POSIX 线程库中,可以使用 pthread_join 函数来实现线程的回收。pthread_join 函数的原型如下:
int pthread_join(pthread_t thread, void **retval);
其中,thread 参数是要回收的线程标识符,retval参数用于接收线程的返回值。函数的返回值表示回收线程的执行状态,如果成功回收线程,则返回 0,否则返回一个非零值,表示出现了错误。
当调用 pthread_join 函数时,当前线程会被阻塞,直到指定的线程执行结束。一旦线程执行结束,它的资源将被回收,并可以通过 retval 参数获取线程的返回值。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
struct persion
{
int num;
int age;
};
void *callback(void* arg)
{
for(int i=0;i<5;++i)
{
printf("子线程:i=%d\n",i);
}
printf("子线程:%ld\n",pthread_self());
struct persion* t = (struct persion*)arg;
t->num=100;
t->age=5;
pthread_exit(&t);
return NULL;
}
int main()
{
pthread_t tid;
struct persion t;
pthread_create(&tid,NULL,callback,&t);
for(int i=0;i<5;++i)
{
printf("主线程:i=%d\n",i);
}
printf("主线程:%ld\n",pthread_self());
void *ptr;
pthread_join(tid,&ptr);
struct persion *pt=(struct persion*)ptr;
printf("num:%d,age:%d\n",t.num,t.age);
pthread_exit(NULL);
return 0;
}
pthread_join其实是个阻塞函数,如果还有子线程在运行,调用该函数就会阻塞,子线程退出函数解除阻塞进行资源的回收,函数被调用一次,只能回收一个子线程,如果有多个子线程则需要循环进行回收。
ptr 用于接收子线程的返回值,pt 通过强制类型转换指向子线程的返回值,而 t 是主线程中的一个独立对象
结果如下所示:
线程分离
线程的分离是指将线程设置为分离状态,使其在退出时自动释放资源,不需要显式地调用 pthread_join 函数来等待线程的结束。
在某些情况下,程序中的主线程有属于自己的业务处理流程,如果让主线程负责子线程的资源回收,调用 pthread_join() 只要子线程不退出主线程就会一直被阻塞,主要线程的任务也就不能被执行了。
线程分离之后在主线程中使用 pthread_join() 就回收不到子线程资源了。
for(int i=0;i<5;++i)
{
printf("子线程:i=%d\n",i);
}
printf("子线程:%ld\n",pthread_self());
struct persion* t = (struct persion*)arg;
t->num=100;
t->age=5;
pthread_exit(&t);
return NULL;
}
int main()
{
pthread_t tid;
struct persion t;
pthread_create(&tid,NULL,callback,&t);
for(int i=0;i<5;++i)
{
printf("主线程:i=%d\n",i);
}
printf("主线程:%ld\n",pthread_self());
pthread_detach(tid);
pthread_exit(NULL);
return 0;
}
线程分离后,子线程执行完毕后被系统内核回收了,且主线程退出后不会影响子线程的执行。