1.线程的概念
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
一个进程可以包含多个线程,这些线程共享进程所拥有的资源,如内存空间、文件描述符等。线程有自己的堆栈、程序计数器等少量的私有数据。
线程的主要优点包括提高程序的并发性,使得多个任务能够在同一进程内并发执行,从而提高系统的资源利用率和响应性能。线程之间的切换相较于进程切换,通常开销更小,因此能够更高效地实现多任务处理。
在多线程编程中,需要注意线程同步和互斥等问题,以确保线程之间能够正确、安全地共享资源和协调工作。
线程是轻量级进程,一般是一个进程中的多个任务。
进程是系统中最小的资源分配单位.
线程是系统中最小的执行单位。
2、线程与进程的区别
资源:
线程比进程多了共享资源。 IPC
线程又具有部分私有资源。
进程间只有私有资源没有共享资源。
空间:
进程空间独立,不能直接通信。
线程可以共享空间,可以直接通信
以下是对线程与进程区别的详细分析:
1. 资源和系统开销
- 进程:进程是操作系统资源分配的基本单位。每个进程都有独立的代码和数据空间(即程序上下文),以及独立的系统资源(如内存、文件描述符等)。进程之间的切换会涉及大量的资源变动,包括内存映射、文件描述符、页表等的切换,因此进程切换的开销相对较大。
- 线程:线程是处理器(CPU)任务调度和执行的基本单位。线程共享进程的代码和数据空间,以及进程所拥有的资源(如内存、文件等)。线程之间的切换主要涉及到线程上下文的切换,包括寄存器状态、栈信息等,因此线程切换的开销相对较小。
2. 并发执行
- 进程:进程是独立的并发执行单位,每个进程都有独立的执行序列和程序入口。进程之间的并发执行是通过操作系统的调度来实现的,进程之间通过进程间通信(IPC)机制进行数据交换和同步。
- 线程:线程是进程中的一个实体,是CPU调度的基本单位。一个进程中的多个线程可以并发执行,共享进程的资源和地址空间。线程之间的通信和同步比进程间通信更为简单和高效。
3. 独立性
- 进程:进程之间是相互独立的,每个进程都拥有独立的地址空间和资源。一个进程的崩溃通常不会影响到其他进程的执行。
- 线程:线程是进程的一部分,多个线程共享进程的资源和地址空间。因此,一个线程的崩溃可能会导致整个进程的崩溃(尽管现代操作系统和编程环境提供了多种机制来减少这种影响)。
4. 健壮性
- 进程:由于进程之间的独立性,一个进程的崩溃通常不会对其他进程造成影响,因此多进程的系统通常比多线程的系统更健壮。
- 线程:由于线程共享进程的资源和地址空间,一个线程的崩溃可能会影响到其他线程的执行,甚至导致整个进程的崩溃。因此,多线程的系统在健壮性方面相对较弱。
5. 使用场景
- 进程:适用于需要高度隔离和独立性的任务,如操作系统中的服务进程、网络服务器中的独立服务等。
- 线程:适用于需要高并发和资源共享的任务,如图形用户界面(GUI)程序中的后台处理线程、服务器程序中的并发处理线程等。
pthread 线程
线程的设计框架 posix
创建多线程 ==》线程空间操作 ===》线程资源回收
3.1 创建多线程:
1、pthread_create
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
功能:该函数可以创建指定的一个线程。
参数:thread 线程id,需要实现定义并由该函数返回。
attr 线程属性,一般是NULL,表示默认属性。
start_routine 指向指针函数的函数指针。本质上是一个函数的名称即可。称为回调函数,是线程的执行空间。
arg 回调函数的参数,即参数3的指针函数参数。
返回值:成功 0
失败 错误码
注意:一次pthread_create执行只能创建一个线程。
每个进程至少有一个线程称为主线程。
主线程退出则所有创建的子线程都退出。
主线程必须有子线程同时运行才算多线程程序。
线程id是线程的唯一标识,是CPU维护的一组数字。
pstree 查看系统中多线程的对应关系。
多个子线程可以执行同一回调函数。
2、线程的退出
正常退出:
线程执行完其函数体后,如果到达了函数的结尾,线程将正常退出。
void* thread_function(void* arg) {
// 线程的工作代码
return NULL; // 线程执行结束,返回NULL
}
使用 pthread_exit
显式退出:
线程可以通过调用 pthread_exit
函数来提前退出。这个函数需要一个指向退出状态的指针,这个状态可以被其他线程通过 pthread_join
获取。
自行退出 ==》自杀 ==》子线程自己退出
void pthread_exit(void *retval);
功能:子线程自行退出
参数: retval 线程退出时候的返回状态,临死遗言。
返回值:无
#include <pthread.h>
void* thread_function(void* arg) {
// 线程的工作代码
pthread_exit(arg); // 使用传递给线程的参数作为退出状态
}
取消线程:
强制退出 ==》他杀 ==》主线程结束子线程
int pthread_cancel(pthread_t thread);
功能:请求结束一个线程
参数:thread 请求结束一个线程tid
返回值:成功 0
失败 -1;
这2个函数默认栈区不释放
3、pthread_self
pthread_t pthread_self(void); unsigned long int; %lu
功能:获取当前线程的线程id
参数:无
返回值:成功 返回当前线程的线程id
失败 -1;
4、线程的回收
线程的回收机制 ====》不同与进程没有孤儿线程和僵尸线程。
====》主线程结束任意生成的子线程都会结束。
====》子线程的结束不会影响主线程的运行。
子线程的回收策略:
1、如果预估子线程可以有限范围内结束则正常用pthread_join等待回收。
2、如果预估子线程可能休眠或者阻塞则等待一定时间后强制回收。
3、如果子线程已知必须长时间运行则,不再回收其资源。
*5.pthread_join
int pthread_join(pthread_t thread, void **retval);
功能:通过该函数可以将指定的线程资源回收,该函数具有
阻塞等待功能,如果指定的线程没有结束,则回收线程
会阻塞。
参数:thread 要回收的子线程tid
retval 要回收的子线程返回值/状态。==》ptread_exit(值);
返回值:成功 0
失败 -1;
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
void* th(void* arg)
{
printf("th tid:%lu\n",pthread_self());
//static char buf[]="我要消亡了";
char * p = (char* )malloc(20);
strcpy(p,"我要消亡了");
sleep(3);
//主要在返回,不要返回局部变量
return p;
}
int main(int argc, char *argv[])
{
pthread_t tid;
pthread_create(&tid,NULL,th,NULL);
void* ret=NULL;
pthread_join(tid,&ret);
printf("ret %s\n",(char*)ret);
free(ret);
return 0;
}
调用 pthread_join
等待新线程结束,并将线程的返回值存储在 void*
类型的变量 ret
中。
将 ret
转换为 char*
并打印其内容,即 "我要消亡了"&ret
。在这里用于获取 ret
的地址,并将这个地址传递给 pthread_join
函数,以便 pthread_join
可以将线程的返回值存储到 ret
指向的内存位置。
6、传参数
线程的传参的类型为void **retval,在这里传的是**retval,这个是指针的指针,含义大多为改变指针的指向,对于函数传参来说,一个*代表传值,而**代表传一个指针的指向。
void* th(void* arg)
{
char * tmp = (char*)arg;//因为传过来的参数,是void *,所以在这里需要强转为char *
printf("tmp %s\n",tmp);
strcat(tmp,"123");
printf("tmp 2 %s\n",tmp);
return tmp;
}
int main(int argc, char *argv[])
{
char buf[128]="hello,world";
pthread_t tid;
pthread_create(&tid,NULL,th,buf);
void* ret=NULL;
pthread_join(tid,&ret);
printf("ret %s\n",(char*)ret);
//free(ret);
return 0;
}
但大多数来说,如果传入的参数不止一个,且类型不同的参数时,要用结构体来进行传参。在子线程接收时,也要进行强制类型转换,转换为结构体类型。代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
typedef struct
{
char buf[50];
int num;
}ARG;
void* th(void* arg)
{
ARG* tmp = (ARG*)arg;
strcat(tmp->buf,"123456");
tmp->num +=10;
return tmp;
}
int main(int argc, char *argv[])
{
ARG arg={0};
bzero(&arg,sizeof(arg));
strcpy(arg.buf,"hello");
arg.num = 20;
pthread_t tid;
pthread_create(&tid,NULL,th,&arg);
void* ret=NULL;
pthread_join(tid,&ret);
printf("ret %s %d\n",((ARG*)ret)->buf,((ARG*)ret)->num);//主线程调用时,也需要强转一下。
return 0;
}
7、分离属性
设置分离属性,目的线程消亡,自动回收空间。(此时不能用join)
指的是线程结束时系统会自动回收其资源,而不需要其他线程调用 pthread_join
来等待它结束。”。在分离状态下,线程结束时会自动释放其占用的资源,而不需要其他线程通过 pthread_join 来显式回收。该子线程结束后,操作系统释放该线程的资源, 需要通过主线程来释放PCB资源
第二种设置分离属性:
int pthread_detach(pthread_t thread);
功能,设置分离属性
参数,线程id号,填自己的id
返回值说明: 线程分离成功返回0,失败返回错误码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
void *th1 (void* arg)
{
pthread_detach(pthread_self());
printf("th1 a is %lu\n",pthread_self());//调用了 pthread_exit,它将不会自动清理其栈空间或局部变量。
pthread_exit(NULL);
}
int main(int argc, char *argv[])
{
pthread_t tid1;
int i = 0 ;
for(i = 0 ;i<55000;i++)
{
int ret = pthread_create(&tid1,NULL,th1,NULL);
if(0!=ret)
{
printf("i is %d\n",i);
break;
}
}
printf("i is %d\n",i);
return 0;
}
8.pthread_cleanup_push pthread_cleanup_pop
线程清理函数是在线程退出时自动调用的函数。它用于释放线程在运行过程中申请的资源,确保程序不会出现资源泄漏,在大型项目中子线程申请的资源可能会在别的函数里使用,所以不可能该子线程结束了就清除资源,应该在其它地方使用后,统一清除这些资源才用这2个函数清除资源pthread_cleanup_push 和 pthread_cleanup_pop。
void pthread_cleanup_push(void (*routine)(void *), void *arg);
功能:注册一个线程清理函数
参数,routine,线程清理函数的入口
arg,清理函数的参数。
返回值,无
void pthread_cleanup_pop(int execute);
功能:调用清理函数
execute,非0 执行清理函数
0 ,不执行清理
返回值,无
这2个函数要成对出现
#include <stdio.h>
#include <pthread.h>
void cleanup(void* arg) {
printf("Cleanup: %s\n", (char*)arg);
}
void* thread_func(void* arg) {
pthread_cleanup_push(cleanup, "Thread Exiting"); // 注册清理函数
printf("Thread is running\n");
sleep(2); // 模拟一些工作
// 使用 cleanup pop 来控制是否调用清理函数
pthread_cleanup_pop(1); // 传递 1,执行清理函数
return NULL;
}
int main() {
pthread_t thread;
pthread_create(&thread, NULL, thread_func, NULL);
pthread_join(thread, NULL); // 等待线程结束
return 0;
}
练习:用函数指针数组创建10个线程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
void *th(void*arg)
{
printf("hello:%lu\n",pthread_self());
}
int main(int argc, const char *argv[])
{
pthread_t tid[10]={0};
int i=0;
void * (*p[10])(void*)={th,th,th,th,th,th,th,th,th,th};
for(i=0;i<10;i++)
{
pthread_create(&tid[i],NULL,p[i],NULL);
}
for(i=0;i<10;i++)
{
pthread_join(tid[i],NULL);
}
printf("all pthread_join\n");
return 0;
}
练习:创建一个线程,主线程接收表达式,填充结构体。传递给th线程,th线程计算结果。计算完后,返回给主线程,主线程显示结果。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
typedef struct
{
float a;
float b;
char c;
float d;
}JSQ;
void *Jsq(void*args)
{
JSQ*add=(JSQ*)args;
if((add->c)=='+')
(add->d)=(add->a)+(add->b);
if((add->c)=='-')
(add->d)=(add->a)-(add->b);
if((add->c)=='*')
(add->d)=(add->a)*(add->b);
if((add->c)=='/')
(add->d)=(add->a)/(add->b);
return add;
}
int main(int argc, const char *argv[])
{
float a,b;
char c;
scanf("%f",&a);
scanf("%c",&c);
scanf("%f",&b);
JSQ s={a,b,c,0};
pthread_t tid;
pthread_create(&tid,NULL,Jsq,&s);
void *ret=NULL;
pthread_join(tid,&ret);
printf("%f\n",((JSQ*)ret)->d);
return 0;
}