谷动谷力

 找回密码
 立即注册
查看: 589|回复: 0
打印 上一主题 下一主题
收起左侧

Linux 多线程详解 —— 线程创建、终止、等待、分离

[复制链接]
跳转到指定楼层
楼主
发表于 2023-8-3 15:37:37 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
Linux 多线程详解 —— 线程创建、终止、等待、分离线程创建
接口:int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
参数解释
  • thread:线程标识符,是一个出参
  • attr:线程属性
  • star_routine:函数指针,保存线程入口函数的地址
  • arg:给线程入口函数传参
返回值:成功返回0,失败返回error number
详解:
  • 第一个参数是pthread_t类型的指针, 线程创建成功的话,会将分配的线程ID填入该指针指向的地址。 线程的后续操作将使用该值作为线程的唯一标识。
  • 第二个参数是pthread_attr_t类型, 通过该参数可以定制线程的属性, 比如可以指定新建线程栈的大小、 调度策略等。 如果创建线程无特殊的要求, 该值也可以是NULL, 表示采用默认属性。
  • 第三个参数是线程需要执行的函数。 创建线程, 是为了让线程执行一定的任务。 线程创建成功之后, 该线程就会执行start_routine函数, 该函数之于线程, 就如同main函数之于主线程。
  • 第四个参数是新建线程执行的start_routine函数的入参。
pthread_create错误码及描述:
返回值
描述
EAGAIN
系统资源不够,或者创建线程的个数超过系统对一个进程中线程总数的限制
EINVAL
第二个参数attr值不合法
EPERM
没有合适的权限来设置调度策略或参数
传入参数arg的选择
传入参数
分析
是否可行
临时变量
临时变量的生命周期,临时变量的值会改变,传递临时变量有可能导致越界的问题
不可行
结构体对象
和临时变量相同
不可行
结构体指针
释放时,在线程不会使用该指针以后
可行
this指针
可行
不要使用临时变量传参,使用堆上开辟的变量可以。
例:
#include <stdio.h>#include <stdlib.h>#include <pthread.h>#include <unistd.h>void *ThreadWork(void *arg){  int *p = (int*)arg;  printf("i am work thread:%p,   data:%d\n",pthread_self(),*p);  pthread_exit(NULL);}int main(){  int i = 1;  pthread_t tid;  int ret = pthread_create(&tid,NULL,ThreadWork,(void*)&i);//不要传临时变量,这里是示范  if(ret != 0)  {    perror("pthread_create");    return -1;  }  while(1)  {    printf("i am main work thread\n");    sleep(1);  }  return 0;}
线程ID以及进程地址空间
线程获取自身的ID:
#include <pthread.h>pthread_t pthread_self(void);
判断两个线程ID是否对应着同一个线程:
#include <pthread.h>int pthread_equal(pthread_t t1, pthread_t t2);
返回为0时,则表示两个线程为同一个线程,非0时,表示不是同一个线程。
用户调用pthread_create函数时, 首先要为线程分配线程栈, 而线程栈的位置就落在共享区。 调用mmap函数为线程分配栈空间。 pthread_create函数分配的pthread_t类型的线程ID, 不过是分配出来的空间里的一个地址, 更确切地说是一个结构体的指针。

即:

线程注意点
  • 线程ID是进程地址空间内的一个地址, 要在同一个线程组内进行线程之间的比较才有意义。 不同线程组内的两个线程, 哪怕两者的pthread_t值是一样的, 也不是同一个线程。
  • 线程ID就有可能会被复用:

    1.线程退出。
    2.线程组的其他线程对该线程执行了pthread_join, 或者线程退出前将分离状态设置为已分离。
    3.再次调用pthread_create创建线程。
线程创建出来的默认值
线程创建的第二个参数是pthread_attr_t类型的指针, pthread_attr_init函数会将线程的属性重置成默认值。
线程属性及默认值:
属性
默认值
说明
contentionscope
PTHREAD_SCOPE_SYSTEM
进程调度相关,线程只支持在OS范围内竞争CPU资源
Detach state
PTHREAD_CREATE_DETACHED
可分离状态
Stack address
NULL
不指定线程开辟的基地址
Stack size
8196(KB)
默认线程栈大小为8M
Guard size
0
警戒缓冲区
Scheduling priority
0
进程调度相关,优先级为0
Scheduling policy
SCHED_OTHER
进程调度相关,调度策略为SCHED_OTHER
Inherit scheduler
PTHREAD_EXPLICIT_SCHED
进程调度相关,继承启动进程的调度策略
如果确实需要很多的线程, 可以调用接口来调整线程栈的大小:
#include <pthread.h>int pthread_attr_setstacksize(pthread_attr_t *attr,size_t stacksize);int pthread_attr_getstacksize(pthread_attr_t *attr,size_t *stacksize);
线程终止
线程终止,但进程不会终止的方法:
  • 入口函数的return返回,线程就退出了
  • 线程调用pthread_exit(NULL),谁调用谁退出
#include <pthread.h>void pthread_exit(void *retval);
参数:retval是返回信息,”临终遗言“,可以给可以不给
该变量不能使用临时变量。
可使用:全局变量、堆上开辟的空间、字符串常量。
pthread_exit和线程启动函数(start_routine) 执行return是有区别的。 在start_routine中调用的任何层级的函数执行pthread_exit() 都会引发线程退出, 而return, 只能是在start_routine函数内执行才能导致线程退出。
  • 其它线程调用了pthread_cancel函数取消了该线程
int pthread_cancel(pthread_t thread);
thread:线程标识符
调用该函数的执行流可以取消其它线程,但是需要知道其它线程的线程标识符,也可以执行流自己取消自己,传入自己的线程标识符。
如果线程组中的任何一个线程调用了exit函数, 或者主线程在main函数中执行了return语句, 那么整个线程组内的所有线程都会终止。
线程等待线程等待接口#include <pthread.h>int pthread_join(pthread_t thread, void **retval);
参数
解释
thread
要等待的线程标识符
retval
接收返回值:若是return退出的,接受入口函数的返回值,pthread_exit退出的,接受该函数的参数,pthread_cancel退出的,void** 保存的是PTHREAD_CANCLED = (void *)-1
调用该函数,该执行流在等待线程退出的时候,该执行流是阻塞在pthread_joind当中的。
线程等待和进程等待的不同
  • 第一点不同之处是进程之间的等待只能是父进程等待子进程, 而线程则不然。线程组内的成员是对等的关系, 只要是在一个线程组内, 就可以对另外一个线程执行连接(join) 操作。
  • 第二点不同之处是进程可以等待任一子进程的退出 , 但是线程的连接操作没有类似的接口, 即不能连接线程组内的任一线程, 必须明确指明要连接的线程的线程ID。
pthread_join()错误码:
返回值
说明
ESRCH
传入的线程ID不存在,查无此线程
EINVAL
线程不是一个joinable线程
EINVAL
已有其它线程捷足先登,链接目标线程
EDEADLK
死锁,如自己链接自己
为什么要等待退出的线程?
如果不连接已经退出的线程, 会导致资源无法释放。 所谓资源指的又是什么呢?
  • 已经退出的线程, 其空间没有被释放, 仍然在进程的地址空间之内。
  • 新创建的线程, 没有复用刚才退出的线程的地址空间。
如果不执行连接操作, 线程的资源就不能被释放, 也不能被复用, 这就造成了资源的泄漏。
纵然调用了pthread_join, 也并没有立即调用munmap来释放掉退出线程的栈, 它们是被后建的线程复用了。 释放线程资源的时候, 若进程可能再次创建线程, 而频繁地munmap和mmap会影响性能, 所以将该栈缓存起来, 放到一个链表之中, 如果有新的创建线程的请求, 会首先在栈缓存链表中寻找空间合适的栈, 有的话, 直接将该栈分配给新创建的线程。
例:
#include <stdio.h>#include <stdlib.h>#include <pthread.h>#include <unistd.h>#include <sys/syscall.h>void *ThreadWork(void *arg){  int *p = (int*)arg;  printf("pid :  %d\n",syscall(SYS_gettid));  printf("i am work thread:%p,   data:%d\n",pthread_self(),*p);  sleep(3);  pthread_exit(NULL);}int main(){  int i = 1;  pthread_t tid;  int ret = pthread_create(&tid,NULL,ThreadWork,(void*)&i);//不要传临时变量,这里是示范  if(ret != 0)  {    perror("pthread_create");    return -1;  }  pthread_join(tid,NULL);//线程等待  while(1)  {    printf("i am main work thread\n");    sleep(1);  }  return 0;}
线程分离
接口:#include <pthread.h>
int pthread_detach(pthread_t thread);
默认情况下, 新创建的线程处于可连接(Joinable) 的状态, 可连接状态的线程退出后, 需要对其执行连接操作, 否则线程资源无法释放, 从而造成资源泄漏。
如果其他线程并不关心线程的返回值, 那么连接操作就会变成一种负担: 你不需要它, 但是你不去执行连接操作又会造成资源泄漏。 这时候你需要的东西只是:线程退出时, 系统自动将线程相关的资源释放掉, 无须等待连接。
可以是线程组内其他线程对目标线程进行分离, 也可以是线程自己执行pthread_detach函数。
线程的状态之中, 可连接状态和已分离状态是冲突的, 一个线程不能既是可连接的, 又是已分离的。 因此, 如果线程处于已分离的状态, 其他线程尝试连接线程时, 会返回EINVAL错误。
pthread_detach错误码:
返回值
说明
ESRCH
传入线程的ID不存在,无此线程
EINVAL
线程不是一个joinable线程,已经处于分离状态
注意:这里的已分离不是指线程失去控制,不归线程组管,而是指线程退出后,系统会自动释放线程资源。若是线程组内的任意线程执行了exit函数,即使是已分离的线程,也仍会收到影响,一并退出。
小编推荐自己的Linux、C/C++技术交流群:【960994558】整理了一些个人觉得比较好的学习书籍、大厂面试题、和热门技术教学视频资料共享在里面(包括C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等等.),有需要的可以自行添加哦!~

以上有不足的地方欢迎指出讨论,觉得不错的朋友希望能得到您的转发支持,同时可以持续关注我,每天分享Linux C/C++后台开发干货内容!





+10
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|深圳市光明谷科技有限公司|光明谷商城|Sunshine Silicon Corpporation ( 粤ICP备14060730号|Sitemap

GMT+8, 2024-5-19 15:48 , Processed in 0.100392 second(s), 44 queries .

Powered by Discuz! X3.2 Licensed

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表