第3篇 Linux多线程--线程生命周期与状态

在上一篇中,我们已经通过知道如何创建多个线程,本篇会谈论到线程的生命周期,在此之前应该了解一下线程在一个进程中的内存布局

主线程和线程栈

每个栈都是一个独立的虚拟内存分配,可以将其放置在任意位置。重要的是要注意,栈的大小通常是有限的。操作系统保留一定的最大的尺寸例如1MB或8MB)。栈不能超过该大小。但是当固定空间用完时,将触发栈溢出。在实践中这不是问题。实际上,超过合理的栈尺寸被认为是一个错误。而由多个段组成。只需添加更多细分即可任意增长。堆由用户模式的C库管理。内核对此一无所知。内核所做的只是在任意的位置提供虚拟内存。

什么是虚拟内存(Virtual Memory),这是涉及到系统内核的内存管理方式足以用一个系列的文章去讲解,以后有机会再说,或者你可以参考相关的文章。

线程栈只是进程所占据的虚拟内存中的一个连续块。 它的最大大小是固定的。 可能看起来像下面的图:


进程中的线程栈布局

我们知道,任何C/C++程序运行时,首先运行main函数,在多线程编程中,这个main函数也被称作初始线程主线程.

  • 1)主线程可以通过pthread_create系统调用创建子线程。
  • 2)主线程和其他子线程获取在整个CPU时钟周期中获得资源调度是均等的,也就是说所有线程默认状态下是异步独立运行的。
  • 3)主线程的确定了所有子线程的生命周期,一但主线程返回或者终止,其他子线程也会终止。
  • 4)主线程接受参数得的方式是通过argc和argv,而普通的线程只有一个参数void*类型的指针
  • 5)一般来说,主线程默认在栈中可以达到足够的长度,而普通的线程栈的是受到限制的。
  • 6)主线程伴随着进程的创建而创建.

线程的生命周期

下面是一个示例,我们在主线程函数暂停5秒,在创建的子线程函数中,我们暂停2秒,目地是检验上面线程的特征的第2点和第3点。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include<string.h>

typedef struct{
    int age;
    char name[15];
} Person;


void *say_hello(void* per){
    sleep(2);
    Person* p=(Person*)per;
    printf("Hi!我的名字叫%s,今年%d岁\n",p->name,p->age);
    return (void*)0;
}
int main(int argc,char* argv){
      pthread_t tid;
      int err;

      Person s;
      memcpy(s.name,"Peter",15);
      s.age=25;
      err=pthread_create(&tid,NULL,say_hello,(void*)(&s));
      if(err!=0){
            printf("创建线程失败\n");
            return 0;
      }

      printf("主线程运行中!!\n");
      sleep(5);
      return 0;
}

Ok,我们的主线程先于子线程打印字符串,而字符串在调用pthread_create就已经运行,但它被延迟2秒才输出。这样就证明所有线程是独立运行的。


Peek 2019-12-31 14-03.gif

如果你我们修改一下上面的实例代码,将main函数中的sleep(5)注释,我们能够看到,子线程没有完全执行之前,主线程已经退出了,从而也强迫子线程跟随终止。

但有时我们的确没必要让主线程等待子线程执行完成后才退出的。使用pthread_exit系统调用可以做到主线程自己执行完后自己退出了(注意:进程没有退出),而子线程仍然可以独立运行直到其退出。

int main(int argc,char* argv){
      pthread_t tid;
      int err;

      Person s;
      memcpy(s.name,"Peter",15);
      s.age=25;
      err=pthread_create(&tid,NULL,say_hello,(void*)(&s));
      if(err!=0){
            printf("创建线程失败\n");
            return 0;
      }

      printf("主线程运行中!!\n");
      int *retval;
      pthread_exit(retval);

线程状态

我们会继续探讨一下线程在运行时的四种状态。

  • 准备等待可用的CPU资源,其他条件一切准备好。当线程被pthread_create创建时或者阻塞状态结束后就处于准备状态。
  • 运行 :线程已经获得CPU的使用权,并且正在运行,在多核心的机器中同时存在多个线程正在运行。如果这种情况不加以控制,会造成整个程序没响应。
  • 阻塞:指一个线程在执行过程中暂停,以等待某个条件的触发。
    • 例如线程可能在处理有关I/O的任务,可能I/O设备繁忙尚未响应或没有可用的I/O缓存。
    • 也可能当前线程等待一个可用的条件便来变量。
    • 错误地对一个已被锁住的互斥量加锁
    • 调用sigwait等待尚未发生的信号。
  • 终止:线程已经从回调函数中返回,或者调用pthread_exit返回,或者被强制终止。
    线程运行时的状态切换

小结

我们本篇讨论了主线程和子线程的关系,下一篇我们会讨论线程在运行时的状态。

推荐阅读更多精彩内容