版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_40028201/article/details/89552238
后台开发核心技术与应用实践学习笔记(九)
第9章 多线程
- 多进程频繁上下文切换会严重影响系统性能;进程间通信要求复杂的系统实现
- 同一进程内部的多个线程共享该进程的所有资源,通过线程可以支持同一个应用程序内部的并发,免去了进程频繁切换的开销;并发任务间的通信变得简单
9.1 多线程是什么
- 单线程一个进程只有一个控制权(函数调用时,该函数获得控制权),多线程允许有多个控制权
- 多线程的进程再内存中有多个栈,多个栈之间以一定的空白区域隔开以备栈的增长
- 由2可知多线程容易导致栈溢出(任何一个空白区域被填满就会导致栈溢出)
9.2 多线程的创建与结束
-
线程创建
pthread_create
创建成功,返回0;pthread_join
用来等待一个线程的结束(还一种是函数已经结束,调用的线程也就结束了),一般主线程调用,等待子线程的退出,是阻塞的。pthread_exit
一般子线程调用,用来结束当前线程。- 子线程可以通过
pthread_exit
传递一个返回值,主线程可以通过pthread_join
获得该返回值,从而判断该子线程的退出时正常还是异常
-
向线程传递参数
pthread_create
函数看起来只能传递一个参数给func,如果传递一个以上的参数,应该放在一个结构体 -
获得线程的id
pthread_self
函数获得线程id- 创建函数时生成id,就是传进的参数thread
9.3 线程的属性
- 属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用,之后必须用pthread_attr_destroy函数来释放资源。
- 属性对象主要包括:作用域(scope)、栈尺寸(stack size)、栈地址(stack address)、优先级(priority)、分离的状态(detached state)、调度策略和参数(scheduling policy and parameters)。默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。
9.4 多线程同步
多线程相当于一个并发系统,一般执行多个任务。如果多个任务可以共享资源,特别是同时写入某个变量时,需要解决同步问题
- 多线程同步问题
- 并发情况下,线程执行顺序由内核决定,是不确定的。
- 多线程同步是指一定的时间内只允许某一个线程访问某个资源
- 方法:互斥锁、条件变量、读写锁、信号量
- 互斥锁
- 有两种状态,一般设置成全局变量,创建方式有两种,动态和静态
- 加锁将原先分离的多个指令构成不可分割的一个原子操作
- 条件变量
- 线程等待共享数据的某个条件出现,需要用到条件变量
- 允许线程阻塞和等待另一个线程发送信号的方法弥补互斥锁的不足。通常与锁一起使用
- 条件不满足,打开相应的互斥锁,一旦某个线程改变了条件变量,通知相应的条件变量唤醒被阻塞的线程,并将这些线程重新锁定并重新检测条件
- 读写锁
- 读时共享,写时独占
- 解决读者写者问题
- 两种常见的策略:强读者同步(读者优先权高,只要写者没操作即可访问资源)、强写者同步(等待所有正在的写者结束后才能执行),why?有啥区别
- 信号量
- 与互斥锁的区别:互斥锁只允许一个线程进入临界区,而信号量允许多个线程进入临界区
- 定义为全局变量,方便多个线程共享
9.5 多线程重入
前面介绍了各种同步方式,其实是为了解决“函数不可重入”的问题,可重入函数指的是多任务并发使用,不用担心数据错误的函数,它可以任意被打断,稍后继续运行,且不会丢失数据。
-
可重入函数特点
1.不在函数内部使用静态或者全局数据
2.不返回静态或者全局数据,所有的数据都由函数调用者提供
3.使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据
4.如果必须访问全局数据,使用互斥锁来保护
5.不调用不可重入函数 -
不可重入函数的特点
1.函数中使用了静态变量,无论是全局静态变量还是局部静态变量
2.函数返回静态变量
3.函数中调用了不可重入函数
4.函数中使用了静态的数据结构
5.调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的
6.调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构 -
_REENTRANT宏
1.它会对部分函数重新定义它们的可安全重入的版本,这些函数名字一般不会发生改变,只是会在函数名后面添加_r字符串,如函数名gethostbyname变成gethostbyname_r。
2.stdio.h中原来以宏的形式实现的一些函数将变成可安全重入函数。
3.在error.h中定义的变量error现在将成为一个函数调用,它能够以一种安全的多线程方式来获取真正的errno的值。