线程满汉全席之线程、多线程概念和线程函数

目录

线程概念篇:

线程(轻量级进程(LWP))概念:

多线程的概念:

标识:pid和tgid的区分

 进程与线程的区别:

多线程和多进程的区别:

 线程函数篇:

线程创建:

线程终止(线程退出):

线程等待:

线程分离:

 主线程退出的状况


线程概念篇:

线程(轻量级进程(LWP))概念:

线程就是一个执行流,创建一个线程就相当于在内核当中创建一个PCB(task_struct),创建出来的PCB当中的内存指针是指向进程的虚拟地址空间的。

但我们存在一个疑问,创建子进程我们知道一个函数vfork,子进程拷贝父进程的PCB,这样子进程可执行父进程的进程虚拟地址空间,这样就存在一个大的问题:调用栈混乱;这也是我们不常用vfork函数的原因,倘若用vfork创建子进程的时候,我们必须让父进程进行进程等待,以避免产生调用栈混乱的问题,这样做和单进程效率一样,且浪费资源。

而我们如果创建一个线程,同样会出现刚才的情况,线程的PCB的内存指针指向进程的虚拟地址空间,这会不会带来vfork函数相同的问题?调用栈混乱

线程满汉全席之线程、多线程概念和线程函数_第1张图片线程满汉全席之线程、多线程概念和线程函数_第2张图片

我们创建多线程的时候,其实并不会和vfork创建多进程一样,出现调用栈混乱的问题!

原因:我们在创建多线程的时候,创建一个线程就等于在内核中创建了PCB,而PCB的内存指针指向进程的虚拟地址空间,如果仅是如此,那肯定会造成调用栈混乱的问题!但是创建线程后,会在共享区开辟一段空间,保存着该线程自己独有的东西,其中就含有调用栈,如此一来,就避免了调用栈混乱的问题。

那在共享区开辟的空间上都保留着那些东西呢?

这就涉及了多线程间共享和独有的问题了。

共享:进程中所有的信息对该进程内的线程都是共享的,eg:进程虚拟地址空间,文件描述符表、信号的处理方式、进程当前的工作路径、用户ID、用户组ID、程序的全局内存、堆内存等等

独有:进程内执行环境所必须的信息,eg:线程ID、栈、errno变量、信号屏蔽字、调度优先级及策略、一组寄存器、线程的私有数据等等

多线程的概念:

由于一个线程就是一个执行流,那么多个线程就是多个执行流,在多核CPU的机器当中,同一时间,每一个执行流理论上都可以拥有一个CPU,然后并行的去运行,多线程可以提高运行的效率

线程的概念是c库当中的概念,因为线程的接口都是c库提供的。底层调用clone接口

同时,承接上面的调用栈混乱的问题:

为了避免多线程出现调用栈混乱的问题,每创建一个子线程,就会在共享区开辟一段空间,保存该线程所独有的东西

线程满汉全席之线程、多线程概念和线程函数_第3张图片

标识:pid和tgid的区分

我们在PCB中即tack_struct结构体中看到两个变量:

pid_t pid;

pid_t tgid;

两个变量都是pid_t类型,他们有什么区别呢?

tgid:(thread group ID) 线程组id, 进程号,即进程内的主线程ID

pid :(process ID)         线程ID,即我们创建的子线程ID

 进程与线程的区别:

一个进程可以包含若干线程,使用线程可以实现应用程序同时做几件事并且互相不干扰。

进程为应用程序的运行实例,是应用程序的一次动态执行,进程是操作系统进行资源分配的单位。

线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源。

线程与同属一个进程的其他线程共享进程所拥有的全部资源。一个线程可以创建和撤销另一个线程,同一进程中的多个线程之间可以并发执行。

多线程和多进程的区别:

多进程:每一个进程都用自己独立的虚拟地址空间,这个也是进程独立性的原因。一个进程的崩溃,不会导致另外一个进程受到影响。

               多进程的程序也可以提高程序运行效率(本质也是增加执行流),但是带来了进程间通信的问题。

多线程:多个线程则共享进程的数据空间,每个线程有自己的执行堆栈和程序计数器为其执行上下文。多线程主要是为了利用CPU时间,同时在一个进程内运行多个任务。

              但由于一个进程内多个线程共用一个虚拟地址空间,所以,一个执行流的异常会导致整个程序的退出,同时多线程还会导致程序健壮性低,代码编写复杂等问题。

 线程/多线程的优缺点

优点:

  1. 创建一个线程所用的资源要比进程小(新建线程仅需要保存一些自己能独立执行程序的必要信息,新建进程会重新开辟一个新的进程虚拟地址空间)
  2. 进程当中的不同线程可以并行的运行,多线程序可以提高程序的运行效率

缺点:

  1. 健壮性/鲁棒性低
  2. 多线程的程序当中有多个执行流,一旦一个执行流异常,会导致整个进程异常。
  3. 缺乏访问控制
  4. 编程难度高,多个执行流可以并发的执行,并发执行的时候,可能会访问同一个临界资源,我们需要对访问临界资源的顺序进行控制,防止程序产生二义性的结果。
  5. 性能损失:线程在调度的时候是有开销的(上下文信息,程序计数器),如果大量的线程,会导致程序在频繁的切换,占用CPU去执行

线程函数篇:

前言:线程函数都是库函数,使用线程函数的时候我们需要链接线程库即增加  -lpthread  选项

线程创建:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

参数:

 thread:线程标识符,帮助我们标识一个线程,其实是一个出参,还记得线程创建的时候,要在共享区开辟弊端空间保存该线程独有的信息嘛,该标识符其实是这段空间的首地址

 attr:线程属性    eg:该线程的栈大小,栈的其实位置,调度优先级、分离属性等等

                                      ulimit -s 命令可以查看栈大小 

           如果我们传递一个nullptr空指针,线程创建的时候将会采用默认属性    

 start_routine:函数指针类型,创建的线程的入口函数,即新建线程要处理的任务函数

 arg:传递给线程入口函数的参数,注意:线程入口函数只能传递一个参数,如果你要传递多个参数,可以考虑自己定义一个结构体,将结构体地址作为arg进行传递

返回值:

      线程创建成功,将会返回0

      线程创建失败,将会返回错误码

 注意arg这个参数,

因为这个参数是传递给新建线程的入口函数的参数,我们要确保这个参数可以被新创建的线程所访问到;

如果我们传递一个临时变量,一定不可以,临时变量存放在主线程的栈上,而我们新创建的线程拥有自己的栈,无法访问主线程的栈,也就无法拿取那个临时变量。

而全局变量是可以的,因为全局变量存放在数据段,数据段是进程内线程共享的;堆也是进程内线程所共享的,我们也可以动态开辟一段空间,传递给新建线程,但注意及时释放这段空间,防止内存泄漏

注意:

同进程创建一样,线程创建时,并不能保证闹个线程先被执行:新建的线程,主线程都有可能先执行 

新建的线程会继承调用线程的信号屏蔽字,但是未决信号集将会被清除

线程终止(线程退出):

线程退出的三种方法:

  • return返回,退出
  • 线程调用pthread_exit()函数
  • 同一进程内的其他线程可以取消该线程

void pthread_exit(void *retval);

         哪个线程调用这个函数,那个线程就会退出;

int pthread_cancel(pthread_t thread);

         我们只需要传入一个线程标识符,就可以让该线程标识符表示的线程退出

         参数:thread:想终止的线程的标识符

         返回值:成功返回0;

                       失败返回错误码

线程等待:

int pthread_join(pthread_t thread, void **retval);

参数:thread:要等待的线程的线程标识符

           retval:一个二级指针,出参,用于获取线程的退出状态

对应线程退出的三种方法:

  • return退出;retval保存的内容是return返回的值
  • pthread_exit(void*)退出;retval保存该函数的参数
  • pthread_cancel(pthread_t);* retval中保存一个常数:PTHREAD_CANCELED

如果我们不关心退出状态,可以传递一个nullptr空指针 

 pthread_join(pthread_t,void**)是一个阻塞接口,直至等待指定的线程退出

等待接口的作用:

线程在默认创建的时候,默认属性当中被认为joinable的

joinable:当线程退出的时候,需要其他线程来回收该线程的资源,如果没有线程回收,则在共享区中,对于退出线程的空间还是保留的,退出线程的资源没有被释放,也就造成了内存泄漏

所以我们调用线程等待接口,可以使指定的线程为分离状态,释放退出线程的资源,防止内存泄漏

如果我们不想进行线程等待,可使用线程分离接口,使线程为分离状态

线程分离:

改变线程的joinable属性,变成detach,从而在线程退出的时候,线程在共享区开辟的资源直接被系统回收,不需要其他线程来回收该线程的资源

int pthread_detach(pthread_t thread);

参数:

   thread:线程标识符

返回值:

   成功返回0;

   失败返回错误码

 主线程退出的状况

我们都知道每个进程中必定有一个主线程,倘若我们调用线程终止接口,是主线程退出,那么整个进程会退出吗?

只要有工作线程程,进程是不会退出的:(主线程类似进入Z状态,称为所谓的“僵尸线程”)

如下面代码所示:主线程退出之后,整个进程不会退出,工作线程状态在R/S之间来回切换

void* start(void* arg)
{
   while(1)
   {   

     printf("I am working\n");    
   }   
}

int main()
{
   pthread_t tid;
   int ret=pthread_create(&tid,NULL,start,NULL);
    if(ret!=0)
    {   
      perror("pthread_create");
      return -1; 
    }   
    pthread_exit(NULL);
    return 0;
}

如果没有工作线程,那么整个进程就会退出

void* start(void* arg)
{
//   while(1)
   {   
    sleep(1);
     printf("I am working\n");    
   }   
}

int main()
{
   pthread_t tid;
   int ret=pthread_create(&tid,NULL,start,NULL);
    if(ret!=0)
    {   
      perror("pthread_create");
      return -1; 
    }   
    sleep(3);
    pthread_exit(NULL);
    return 0;
}

注:如果本篇博客有任何错误和建议,欢迎伙伴们留言,你快说句话啊!

你可能感兴趣的:(Linux,linux,多线程,操作系统,并发编程)