线程的创建和使用

前言:

why用线程?
当一个进程需要另一个实体执行事务时就会调用fork()子进程,但

  • 一、fork()开销很大,因为子进程会把父进程的全部资源拷贝过来。

  • 二、父子进程间通信(IPC)时,从子进程返回信息给父进程很是麻烦。
    线程有个很好的特性就是在一个进程内的所有线程共享同一个全局内存空间,所以在线程间共享信息是很容易的。

线程介绍

线程根据调度者身份可分为内核线程和用户线程
内核线程是一种轻量级进程(light weight process),运行在内核态,由内核调度
用户线程运行在用户空间,由线程库调度。
一个进程中的内核线程数量少于用户线程。这是因为当一个内核线程获得CPU使用权时就会加载并运行一个用户线程。
一个进程中所有执行线程共享该进程的时间片,它们对外表现相同的优先级。即M个用户线程对应一个内核线程,这个内核线程实际上就是进程本身,所以实际上内核仍然把整个进程作为最小单位来调度。

从内核看进程与线程的区别就是它们都有各自不同的PCB,但PCB中指向内存资源的三级页表是相同的。

进程地址的虚拟映射:对于进程来说,相同的地址(同一个虚拟地址)在不同的进程中,反复使用而不冲突。原因是他们虽虚拟址一样,但页目录、页表、物理页面各不相同。相同的虚拟地址,映射到不同的物理页面内存单元,最终访问不同的物理页面。
但在线程中,两个线程具有各自独立的 PCB,共享同一个页目录,也就共享同一个页表和物理页面。所以
两个 PCB 共享一个地址空间。
如果复制对方的地址空间,那么就产出一个“进程”;如果共享对方的地址空间,就产生一个“线程”。

线程与进程比较

一个进程内所有线程共享的资源有:

  • 全局变量
  • 进程指令
  • 文件描述符
  • 信号处理方式
  • 当前工作目录
  • 用户ID和组ID

线程与进程的区别
进程有独立的进程地址空间,有独立的pcb,进程是cpu分配资源的最小单位。
线程有独立的pcb,没有独立的进程地址空间,进程是最小的执行单位

线程与线程之间:(线程不共享的信息)

  • 线程ID
  • 寄存器集合(程序计数器、栈指针…)
  • errno
  • 信号掩码
  • 优先级

linux中查看线程使用命令ps -Lf 进程id,可以查看到此进程中的线程号。

线程原语

编译和链接时 加 -pthread引入线程库(或-lpthread),属于POSIX线程标准(简称pthread)

pthread_t pthread_self(void);获取线程 ID。其作用对应进程中 getpid() 函数
成功返回0,失败无返回
线程ID在linux下为无符号整数(%lu)。

创建:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);创建一个新线程。 对应进程中 fork() 函数。
返回值:成功: 0; 失败:错误码 —>Linux 环境下,所有线程特点,失败均直接返回错误号。
参数:
pthread_t: 当前 Linux 中可理解为: typedef unsigned long int pthread_t;
参数 1:传出参数,保存系统为我们分配好的线程 ID
参数 2:通常传 NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。
参数 3:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。
参数 4:线程主函数执行期间所使用的参数
创建线程后,原线程返回继续向下执行,而新创建的线程由函数指针*start_routine决定。新创建的线程ID被记录到thread参数中传出。

注意:pthread产生的错误码并不保存在errno中,所以不能够用perror()打印错误信息,
打印错误信息应用strerrno()把错误码转换成错误信息再打印。

回收:
int pthread_join(pthread_t thread, void **status);阻塞等待线程退出,获取线程退出状态-------------对比进程waitpid()
thread 线程以不同的方法终止,通过 pthread_join得到的终止状态是不同的,

  1. 如果 thread 线程通过 return 返回, status 所指向的单元里存放的是 thread 线程函数的返回值。
  2. 如果 thread 线程被别的线程调用 pthread_cancel 异常终止掉, status 所指向的单元里存放的是常数
    PTHREAD_CANCELED。
  3. 如果 thread 线程是自己调用 pthread_exit 终止的, status所指向的单元存放的是传给 pthread_exit 的参数。
  4. 如果对 thread 线程的终止状态不感兴趣,可以传 NULL 给 status 参数。

int pthread_detach(pthread_t thread); 设置线程分离,检查出错返回,设置detach分离线程后,分离后的线程会自动回收
thread: 待分离的线程id

返回值:成功:0

失败:errno

设置分离态

int pthread_attr_t attr   创建一个线程属性结构体变量

int pthread_attr_init(&attr); 初始化线程属性   成功: 0;失败:错误号

int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);    获取程属性,分离 or 非分离

int pthread_attr_setdetachstate(&attr,  PTHREAD_CREATE_DETACHED); 设置线程属性为 分离态
   detachstate: PTHREAD_CREATE_DETACHED(分离线程)  
						PTHREAD _CREATE_JOINABLE(非分离线程)
int pthread_create(&tid, &attr, tfn, NULL); 借助修改后的 设置线程属性 创建为分离态的新线程

int pthread_attr_destroy(&attr); 销毁线程属性

如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在 pthread_create
函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用 pthread_create的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用 pthread_cond_timedwait 函数,让这个线程等待一会儿,留出足够的时间让函数 pthread_create 返回。设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如 wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题

int pthread_cancel(pthread_t thread);杀死一个线程。 需要到达取消点(保存点)

thread: 待杀死的线程id

返回值:成功:0

失败:errno

如果,子线程没有到达取消点, 那么 pthread_cancel 无效。

取消点:是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用 creat, open, pause,close, read, write… 执行命令 man 7 pthreads 可以查看具备这些取消点的系统调用列表
线程中没有取消点,可以通过调用 pthread_testcancel()函数自行设置一个取消点
#define PTHREAD_CANCELED ((void *) -1)
成功被 pthread_cancel() 杀死的线程,返回 -1(非正常死亡).使用pthead_join 回收。

void pthread_exit(void *retval);将单个线程退出
参数: retval 表示线程退出状态,通常传 NULL

主线程退出其他线程不退出,主线程应调用 pthread_exit()

exit()函数是退出进程,若进程退出,则进程中所有线程都会退出。
return:返回到调用者那里去。

线程不应与信号量共存
线程间缺乏必要同步机制,应使用锁和条件变量来避免数据竞争

(有关同步,参见进程间通信之同步)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我爱松子鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值