线程与线程控制

线程

①线程定义

Linux中的所有执行流,都叫做轻量级进程(LWP),轻量级进程又叫线程

线程在进程内部运行->线程在进程地址空间内运行
在这里插入图片描述

在Linux中,站在CPU的角度,能否识别该task_struct是进程还是线程?
不能,CPU不需要识别,CPU只关心一个一个的单个执行流,每个task_struct就是一个执行流

在CPU眼中的task_struct<=OS原理的task_struct

在这里插入图片描述

②线程的优点

1.创建一个新线程的代价要比创建一个新进程小得多
2.与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
3.线程占用的资源要比进程少很多
4.计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作

线程资源共享很简单,只需要定义全局资源,所有线程就都可以看到

计算密集型:该执行流的大部分任务,主要以计算为主(加密解密,排序查找)
I/O密集型:该执行流的大部分任务,主要以I/O为主(刷磁盘,访问数据库,访问网络)

③线程的缺点

性能损失:
一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,大量的线程增加了额外的同步和调度开销,所以线程也不是越多越好

健壮性降低:
编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,造成线程安全问题;多线程中,单个线程崩溃会导致整个进程崩溃,且很难找出是哪个线程崩溃,很难调试处理

缺乏访问控制:
进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响

编程难度提高:
编写与调试一个多线程程序比单线程程序困难得多

④线程异常

单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出,资源也被回收

⑤进程与线程

进程是资源分配的基本单位,线程是调度的基本单位

线程独有的数据:
1.线程ID
2.一组寄存器(保存上下文,调度)
3.栈(线程的临时数据)

4.errno
5.信号屏蔽字
6.调度优先级

进程的多个线程共享同一地址空间,因此数据段跟代码段都是共享的,各线程还共享以下进程资源和环境:
1.文件描述符表
2.每种信号的处理方式
3.当前工作目录
4.用户id和组id

POSIX线程库

与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的,引入头文件<pthread.h>
链接这些线程函数库时要使用编译器命令的“-lpthread”选项

①线程的创建

在这里插入图片描述

参数:
thread:线程标识符,输出型参数
attr:线程属性可以指定新建线程栈的大小、 调度策略,如果为NULL就是默认属性
star_routine:线程历程,回调函数,线程的入口函数
arg:给线程入口函数传参,NULL代表没有参数
返回值:成功返回0,失败返回error number

示例:

#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
void *Routine(void* arg)//回调
{
    
    
     char* str=(char*) arg;
     while(1)
     {
    
    
       printf("%s:pid:%d,ppid:%d\n",str,getpid(),getppid());
      sleep(1);
    }
}
  int main()
  {
    
    
    pthread_t pt;
    pthread_create(&pt,NULL,Routine,(void*)"i am a new pthread");
    while(1)
    {
    
    
      printf("i am a main pthread:pid:%d,ppid:%d\n",getpid(),getppid());
      sleep(2);
    }
    return 0;
  }                          

在这里插入图片描述
可以看到虽然是两个执行流,但是却是同一个进程
查看当前的轻量级进程:ps -aL
在这里插入图片描述

②获取线程的ID

在这里插入图片描述
创建六个线程,通过两种方式获取线程ID:

  #include<stdio.h>                                                                                                                                         
  #include<unistd.h>
  #include<pthread.h>
  void *Routine(void* arg)//回调
  {
    
    
    char* str=(char*) arg;
    while(1)
    {
    
    
      printf("%s:pid:%d,ppid:%d,tid:%lu\n",str,getpid(),getppid(),pthread_self());
      sleep(1);
    }
  
 }
  int main()
  {
    
    
    pthread_t tid[6];
    for(int i=0;i<6;i++)
    {
    
    
      char buffer[64];
      sprintf(buffer,"thread %d",i);
      pthread_create(&tid[i],NULL,Routine,(void*)buffer);
      printf("%s tid is:%lu\n",buffer,tid[i]);//通过create的输出型参数拿到线程ID
    }
  
    while(1)
    {
    
    
      printf("i am a main pthread:pid:%d,ppid:%d,tid:%lu\n",getpid(),getppid(),pthread_self());
	  sleep(2);
    }
    return 0;
  }           

在这里插入图片描述
此时获得的线程ID与LWP并不一样
这两种方式获取的都是用户级的线程ID,LWP获取的是内核级的线程ID
Linux中,应用层的线程与内核的LWP是一 一对应的

③线程等待

像进程等待那样,线程也需要被等待来释放资源

为什么需要线程等待?
已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
创建新的线程不会复用刚才退出线程的地址空间

在这里插入图片描述
retval:指向线程的返回值

	 #include<stdio.h>                                                                                                                                       
     #include<stdlib.h>
     #include<unistd.h>
     #include<pthread.h>
     void* Routine(void* arg)//回调
    {
    
    
       char* str=(char*) arg;
       int count=0;
       while(count<5)
      {
    
    
        printf("%s:pid:%d,ppid:%d,tid:%lu\n",str,getpid(),getppid(),pthread_self());
        sleep(1);
        count++;
      }
     return (void*)66666; 
    }
    int main()
    {
    
    
      pthread_t tid[6];
      for(int i=0;i<6;i++)
      {
    
    
        char* buffer=(char*)malloc(64);
        sprintf(buffer,"thread %d",i);
        pthread_create(&tid[i],NULL,Routine,(void*)buffer);
        printf("%s tid is:%lu\n",buffer,tid[i]);//通过create的输出型参数拿到线程ID
      }
    
      for(int i=0;i<6;i++)
      {
    
    
		 void* ret=NULL;//返回码
         pthread_join(tid[i],&ret);//默认阻塞式等待
         printf("i am a main pthread:pid:%d,ppid:%d,tid:%lu,code:%d\n",getpid(),getppid(),pthread_self(),(int)ret);
      }
      printf("wait finish\n");
      return 0;
    }           

在这里插入图片描述
线程只有退出码,并不像进程那样还有异常信息,原因是线程一旦异常,整个进程都终止,主线程没有join获取的机会,线程健壮性低

④线程终止

1.exit:终止进程 线程调用会直接终止整个进程
2.pthread_exit:终止该线程,相当于return
3.pthread_cancel:取消线程,一般是主线程取消新线程,退出码-1

在这里插入图片描述
在这里插入图片描述
不建议:
新线程取消主线程:主线程被取消后,后续代码不再执行,而新线程并没有被影响,继续执行代码

	#include<stdio.h>                                                                                                                           
    #include<stdlib.h>
    #include<unistd.h>
    #include<pthread.h>
    pthread_t main_thread;
    void* Routine(void* arg)//回调
    {
    
    
      char* str=(char*) arg;
      int count=0;
      while(count<5)
      {
    
    
        printf("%s:pid:%d,ppid:%d,tid:%lu\n",str,getpid(),getppid(),pthread_self());
        sleep(1);
        int ret= pthread_cancel(main_thread);
        printf("%d",ret);                                                 
        printf("已经取消\n");
        count++;
      }
      return NULL;
    }
    
    int main()
    {
    
    
      pthread_t tid[6];
      main_thread=pthread_self();
      for(int i=0;i<6;i++)
      {
    
    
		char* buffer=(char*)malloc(64);
        sprintf(buffer,"thread %d",i);
        pthread_create(&tid[i],NULL,Routine,(void*)buffer);
        printf("%s tid is:%lu\n",buffer,tid[i]);//通过create的输出型参数拿到线程ID
      }

     for(int i=0;i<6;i++)
      {
    
     
        void* ret=NULL;//返回码
        pthread_join(tid[i],&ret);//默认阻塞式等待
        printf("i am a main pthread:pid:%d,ppid:%d,tid:%lu,code:%d\n",getpid(),getppid(),pthread_self(),(int)ret);
      }
      printf("wait finish\n");
      return 0;
    }                         

在这里插入图片描述
第一次取消成功返回0,后面取消失败返回3
在这里插入图片描述
defunct表示线程已经被取消

⑤线程分离

一般情况下线程必须被等待,就像进程被等待一样

1.线程可以不被join,但是这就需要对线程进行分离
2.线程分离后线程退出,系统会自动回收线程资源,这样就不需要被join阻塞等待了
3.如果不关心线程的返回值,join是一种负担,就用线程分离
4.一个线程不能是既是joinable又是分离的

在这里插入图片描述

  #include<stdio.h>                                                                                                                             
  #include<stdlib.h>
  #include<unistd.h>
  #include<pthread.h>
  pthread_t main_thread;
  void* Routine(void* arg)//回调
  {
    
    
    sleep(1);
    pthread_detach(main_thread);
    char* str=(char*) arg;
    int count=0;
    while(count<5)
    {
    
    
      printf("%s:pid:%d,ppid:%d,tid:%lu\n",str,getpid(),getppid(),pthread_self());
      sleep(1);
      count++;
    }
    return NULL;
  }
  
  int main()
  {
    
    
    pthread_t tid[6];
    main_thread=pthread_self();
    for(int i=0;i<6;i++)
    {
    
    
      char* buffer=(char*)malloc(64);
	  sprintf(buffer,"thread %d",i);
      pthread_create(&tid[i],NULL,Routine,(void*)buffer);
      printf("%s tid is:%lu\n",buffer,tid[i]);//通过create的输出型参数拿到线程ID
    }
    while(1)
    {
    
    
      printf("i am a main pthread:pid:%d,ppid:%d,tid:%lu\n",getpid(),getppid(),pthread_self());
      sleep(1);
    }
    printf("wait finish\n");
    return 0;
  }                                 

在这里插入图片描述
分离的线程退出,系统自动回收资源

线程ID及进程地址空间布局

thread_t本质上是一个地址

线程也需要被管理,Linux不提供真正的线程,只提供LWP,OS只对LWP内核执行流管理,而供用户使用的接口等其他数据由线程库pthread管理,意味着在库里面进行先描述再组织
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

二级页表

64位下采用多级页表,32位实际上采用二级页表映射
当映射关系存在一张表时,表大小太大,需要通过二级页表来存储
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/hbbfvv1h/article/details/122759664
今日推荐