线程创建、线程终止、线程等待及线程分离

2019-07-14 11:00发布

线程的概念

1.什么是线程:是一个轻量级进程,一个进程内的一条执行流,也可以说是一个进程内的控制序列。
  • linux下PCB是线程
    1. 说的是linux操作系统,其他操作系统不是这样
    2. linux对线程和进程不作区分,linux下的线程以进程PCB模拟
    3. linux下的线程以进程PCB模拟,因此也叫轻量级进程(LWP)
    4. 一个进程中至少有一个线程,进程也叫线程组
  • CPU足够的情况下,多个进程可同时运行,CPU调度进程的时候,调度的是PCB
  • 因为多个进程有多个PCB,因此多个进程可以实现并行
2.进程和线程的关系
  • 进程是具有一个或者多个线程的线程组
  • 线程共享进程数据,同时也拥有自己的一部分数据,线程ID、一组寄存器、栈、errno、信号屏蔽字及调度优先级。
  • 进程是操作系统资源分配的基本单位(ps -ef)
  • 线程是CPU调度的基本单位,或者说是程序执行的最小单位
线程与进程的优缺点:
  • 线程优点
    1. 一个进程中可能会有多个线程,而这些线程共享一个虚拟地址空间,因此有时候也会说线程是运行在进程中的
    2. 线程间通信将变得极为方便
    3. 线程占用的资源要比进程小很多,能充分利用多处理器的可并行数量
    4. 创建/销毁一个线程相较于进程来说成本更低(不需要额外创建虚拟地址空间)
    5. 线程的调度切换相较于进程也较低
  • 线程缺点
    1. 一个进程中可能会有多个线程,而这些线程共享一个虚拟地址空间
    2. 线程间的数据访问变得更加简单,因此数据访问安全问题更加突出,要考虑的问题增多,编码难度增加
    3. 一些系统调用和异常都是针对整个进程的,因此当一个线程中出现了异常,那么整个进程都会崩溃,以及一些系统调用的使用也要注意
  • 进程的优点:安全,稳定(因为进程的独立性)
进程ID和线程ID 每一个用户态的线程,在内核中都有一个调度实体,也拥有自己的描述符,即task_struct结构体,在task_struct有一个成员叫PID这个是进程PID,但是现在看进程的话,一个进程中有可能会有多个进程,也就是说会有多个PID 因为现在的进程是线程组,其实在每一个task_struct都有一个标识说他们是哪个线程组的线程,这个成员叫TGID即线程组ID 然而这个线程组ID(TGID)其实等于进程中主线程(首线程)task_struct中的PID, 而用户态ps -ef显示的进程信息其实是首线程信息,通过ps -efL查看进程内的所有线程信息 LWP---轻量级进程,这一列记录的就是每个线程task_struct中的pid NLWP--线程组内线程的个数 PID--用户态展现出来的PID这一列其实显示的是TGID的值 struct task_struct { pid_t pid; pid_t tgid; ..... struct task_struct *group_leader; ..... struct list_head thread_group; ..... }

线程控制

    操作系统并没有提供系统调用来创建线程,所以就在posix标准库中,实现的一套线程控制(创建/终止/等待..)的接口     因为这套接口创建的线程是库函数(以pthreaed_ 开头),是用户态的线程创建,因此创建的线程也叫用户线程    在操作系统内部,一个用户线程对应了一个轻量级进程来进行调度运行 线程创建 int pthread_create(pthread_t *thread, const pthread_attr_t *attr,                           void *(*start_routine) (void *), void *arg);
  • 功能:线程的创建(创建用户线程),并且通过第一个参数返回了一个用户线程ID
  • thread:用于接收一个用户线程ID,其实是一个指向线程自己的线程地址空间在整个进程虚拟地址空间中的位置
  • attr:用于设置线程属性,一般置NULL
  • start_routine:线程的入口函数,线程运行的就是这个函数,这个函数退出了,线程也就退出了
  • arg:用于给线程入口函数传递参数
  • 返回值:0-成功,errno-失败
每一个线程都需要有自己的栈区,否则如果所有线程公用一个栈的话,会引起调用栈混乱,并且因为CPU是以PCB来调度的,因此线程是CPU调度的基本单位,所以每个线程也都应该有自己的上下文数据来保存CPU调度切换时的数据。(而这些都是在线程地址空间中,每一个线程都有自己的线程地址空间,他们相对来说独立,但是因为线程地址空间是在虚拟地址空间内的)   线程没有父子之分,所有的线程都是平级的,如果非要说有区别那么就是主线程与其它线程之分。     pthread_t pthread_self(void) 获取调用现成的线程id(用户态线程id) 1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 8 void* thr_start(void *arg) 9 { 10 int num = (int)arg; 11 12 while(1) { 13 printf("talk with girl friend:%d ", num); 14 sleep(1); 15 } 16 return NULL; 17 } 18 int main() 19 { 20 //int pthread_create(pthread_t *thread, pthread_attr_t *attr, 21 // void *(*start_routine) (void *), void *arg); 22 // 功能:线程的创建(创建用户线程) 23 // thread: 用于接收一个用户线程ID 24 // attr: 用于设置线程属性,一般置NULL 25 // start_routine:线程的入口函数 26 // 线程运行的就是这个函数,这个函数退出了,线程也就退出了 27 // arg: 用于给线程入口函数传递参数 28 // 返回值:0-成功 errno-失败 29 pthread_t tid; 30 int ret; 31 ret = pthread_create(&tid, NULL, thr_start, (void*)999); 32 if (ret != 0) { 33 perror("pthread_create error"); 34 return -1; 35 } 36 //pthread_t pthread_self(void); 37 // 获取调用线程的线程id(用户态线程id) 38 printf("main thread id:%p ", pthread_self()); 39 printf("child pthread id:%p ", tid); 40 while(1) { 41 printf("play game!! "); 42 sleep(1); 43 } 44 return 0; 线程终止:线程的退出 线程退出的方式
  1. return退出:在线程中可以调用return退出,但是在main函数中调用return效果是退出进程
  2. 线程可以调用pthread_exit终止自己
    • #include
    • void pthread_exit(void *retval);
    • 谁调用谁退出,退出的是线程,无返回值
    • pthread_exit或return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针的时候线程函数已经退出了
  3. 一个线程可以调用pthread_cancel终止同一进程中的另一个进程
    • #include
    • int pthread_cancel(pthread_t thread);
  •  取消线程id为thread的线程,即让thread线程退出
  • 返回值为-1  1 /* 这段代码用于演示线程退出的几种方式 2 */ 3 4 #include 5 #include 6 #include 7 #include 8 9 //线程 的退出演示1:return 10 void *thr_start(void *arg) 11 { 10 void *thr_start(void *arg) 11 { 12 //1. 在线程中调用exit函数会怎样?--直接退出进程 13 // 进程要是退出了,那么进程中的所有线程也就退出了 14 //exit(0); 15 16 while(1) { 17 printf("child pthread!! "); 18 sleep(1); 19 } 20 return NULL; 21 } 22 int main() 23 { 24 pthread_t tid; 25 int ret = -1; 26 27 ret = pthread_create(&tid, NULL, thr_start, NULL); 28 if (ret != 0) { 29 printf("pthread_create error "); 30 return -1; 31 } 32 //pthread_exit(NULL); 33 //int pthread_cancel(pthread_t thread); 34 // 取消线程id为thread的线程,即让thread线程退出 35 pthread_cancel(tid); 36 while(1) { 37 printf("main thread!!! "); 38 sleep(1); 39 }
线程等待 为什么要线程等待?等待到底是干什么? 线程退出实际上也会形成僵尸线程占用了一部分资源不释放,最终造成资源泄露 线程等待就是接收线程的返回值,然后释放线程的所有资源 #include  int pthread_join(pthread_t thread, void **retval);
  • 功能:等待指定的线程并获取返回值,避免资源泄露
  • thread:用于指定等待哪个线程
  • retval:用于接收线程的返回值
  • 如果调用join函数的时候线程已经退出了,那么将直接返回,否则将陷入阻塞等待,直到这个指定的线程退出
线程有一个属性:joinable
  • 如果线程的属性是处于joinable状态,代表这个线程退出后需要被别人等待
  • 一个线程只有处于joinable状态,才能被等待
  • 一个线程创建出来后,默认属性就是joinable状态
  • 而这个等待就是获取线程的返回值,并释放线程资源
  • 如果一个线程是被取消的,那么它的返回值是-1,PTHREAD_CANCELD
1 /* 这是一段演示线程等待获取线程返回值的代码 2 * 如何线程等待? 3 * int pthread_join(pthread_t thread, void **retval); 4 * 线程有一个属性:joinable 5 * 如果线程的属性是处于joinable状态,代表这个线程退出后需要被 6 * 别人等待。 7 * 一个线程只有处于joinable状态,才能被等待 8 * 一个线程创建出来后,默认属性就是joinable状态 9 * 而这个等待就是获取线程的返回值,并释放线程资源 10 */ 11 12 #include 13 #include 14 #include 15 #include 16 #include 17 18 void *thr_start(void *arg) 19 { 20 sleep(1); 21 pthread_exit("to be bit people!!"); 22 return "hello bit!!! "; 23 } 24 int main() 25 { 26 pthread_t tid; 27 28 int ret = pthread_create(&tid, NULL, thr_start, NULL); 29 if (ret != 0) { 30 printf("pthread_create error "); 31 return -1; 32 } 33 //int pthread_join(pthread_t thread, void **retval); 34 // 等待指定的线程并获取返回值 35 // thread: 用于指定等待哪个线程 36 // retval: 用于接收线程的返回值 37 // 如果调用join函数的时候线程已经退出了,那么将直接返回,否则 38 // 将陷入阻塞等待,直到这个指定的线程退出 39 pthread_cancel(tid); 40 sleep(5); 41 char *ptr; 42 pthread_join(tid, (void **)&ptr); 43 printf("child thread say:%d ", (int)ptr); 44 return 0; 45 } 线程分离 我们等待一个线程是因为需要获取线程的返回值,并释放资源,那么假如我根本不关心返回值,那么这个等待将毫无意义仅仅是为了释放资源,因此就有了一个线程属性叫:线程分离属性    detach属性,这个属性需要设置,他是告诉操作系统,这个指定的线程我不关心返回值,所以如果退出了,就自动把所有资源回收。 而设置一个线性分离属性我们常称为 分离线程 线程的detach属性与joinable属性相对应,也相冲突,两者不会同时存在。如果一个线程属于detach,那么调用pthread_join的时候将直接报错,所以我们在上边说,只有一个线程处于joinable状态才可以被等待 #include int pthread_detach(pthread_t thread);
  • 功能:分离指定的线程
  • 这个分离后的线程退出后将自动回收资源
  • pthread_detach并不会阻塞,只是设置了一个线程属性
1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 8 void* thr_start(void *arg) 9 { 10 int num = (int)arg; 11 12 pthread_detach(pthread_self()); 13 while(1) { 14 printf("talk with girl friend:%d ", num); 15 sleep(1); 16 } 17 return NULL; 18 } 19 int main() 20 { 21 pthread_t tid; 22 int ret; 23 ret = pthread_create(&tid, NULL, thr_start, (void*)999); 24 if (ret != 0) { 25 perror("pthread_create error"); 26 return -1; 27 } 28 //int pthread_detach(pthread_t thread); 29 // 分离指定的线程 30 // 这个分离后的线程退出后将自动回收资源 31 // pthread_detach并不会阻塞,只是设置了一个线程属性 32 pthread_detach(tid); 33 printf("main thread id:%p ", pthread_self()); 34 printf("child pthread id:%p ", tid); 35 while(1) { 36 printf("play game!! "); 37 sleep(1); 38 } 39 return 0; 40 }