linux:线程及其相关函数,线程的回收

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 
 
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 
#include 
#include 
#include 
#include 
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 
#include 
#include 
#include 
#include 
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 
#include 
#include 
#include 
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 
#include 
 
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 
#include 
#include 
#include 
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 
#include 
#include 
#include 
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;
}

你可能感兴趣的:(java,jvm,开发语言)