马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
10. pthread_t 范例
留意:
- 每一个线程的库级别的tcb的起始地点,就是线程的 tid
- 每一个线程都有本身独立的栈结构
- 线程和线程之间,也是可以被其他线程看到并访问的(比如全局函数)
代码
如果想要历程拥有私人的全局变量(即线程库内里的线程局部存储):
__stread 修饰 变量(只能是内置范例的,不能是自界说范例的) ,如:
__stread int val
留意:
__stread 是编译选项
代码
11. linux线程互斥
(一)历程线程间的互斥干系配景概念
- 临界资源:多线程实行流共享的资源就叫做临界资源
- 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
- 互斥:任何时间,互斥包管有且只有一个实行流进入临界区,访问临界资源,通常对临界资源起掩护作用
- 原子性(背面讨论怎样实现):不会被任何调治机制打断的利用,该利用只有两态,要么完成,要么未完成
- 多线程并发访问共享资源的时间,容易发生数据不划一标题
抢票举例
if ( tickets > 0) // 判断
{
tickets--; //抢到票了
}
留意:
由于是 tickets-- 这一步是三条汇编指令去实行
- 把 tickets 拷贝到CPU寄存器中
- CPU寄存器的值--
- CPU寄存器的值拷贝到内存
发生错误大概缘故原由:
- if 语句判断条件为真以后,代码可以并发的切换到其他线程
- tickets-- 利用本身就不是一个原子利用
留意:
要办理数据不划一标题,必要做到三点:
- 代码必须要有互斥活动:当代码进入临界区实行时,不答应其他线程进入该临界区。
- 如果多个线程同时要求实行临界区的代码,而且临界区没有线程在实行,那么只能答应一个线程进入该临界区
- 如果线程不在临界区中实行,那么该线程不能克制其他线程进入临界区
要做到这三点,本质上就是必要一把锁。Linux上提供的这把锁叫互斥量
(二)互斥量
互斥量干系函数
初始化互斥量
初始化互斥量有两种方法:
1. 静态分配:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
留意:
此时不必要对 pthread_mutex_t 范例变量做初始化和烧毁处理惩罚(即不必要调用 pthread_mutex_init 和 pthread_mutex_destroy 函数)
2. 动态分配:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict
attr);
参数:
mutex:要初始化的互斥量
attr:互斥量的属性,一样寻常设置成NULL
留意:
这个函数肯定要搭配 pthread_mutex_destroy一起利用
烧毁互斥量
int pthread_mutex_destroy(pthread_mutex_t *mutex);
留意:
- 不要烧毁一个已经加锁的互斥量
- 已经烧毁的互斥量,要确保背面不会有线程再实行加锁
互斥量加锁息争锁
加锁 :int pthread_mutex_lock(pthread_mutex_t *mutex);
解锁 :int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:乐成返回0,失败返回错误号
留意:
1. 调用 pthread_ lock 时,大概会碰到以下环境:
互斥量处于未锁状态,该函数会将互斥量锁定,同时返回乐成
互斥量处于加锁状态,该线程会被壅闭,实行流处于被挂起,等候利用体系唤醒(即互斥量解锁了)
2. 哪个线程都可以对互斥量解锁,和加锁的线程是哪个无关
3. 纵然只有一个线程,也大概由于利用不当,被壅闭挂起(比如:调用了两次加锁)
4. 如果申请了锁的资源,肯定要把它开释掉
5. 加了锁的临界区的代码发生线程切换,也不会影响结果的精确
(三)加锁
- 加锁的本质:用时间来换安全
- 加锁的体现:线程对于临界区代码串行实行
- 加锁的原则:只管要包管临界区的代码少
留意:
- 纯互斥环境下,如果锁分配的不公道,容易导致其他线程的饥饿标题(线程对锁的竞争本领大概差别,比如一个线程开释了锁,又能立即拿到锁资源,导致其他线程不停壅闭)
- 不愿定是有互斥,必有饥饿,纯互 m斥的场景,就用互斥
- 办理饥饿:让全部的线程获取锁资源,是按肯定的序次获取 --------- 叫同步
(三)互斥性原理
锁也是共享资源,为了维护其他临界资源,应该先掩护本身的资源
锁的实现原理:
为了实现互斥锁利用,大多数体系结构都提供了swap或exchange指令(在硬件芯片上都有对应的指令集),该指令的作用是把寄存器和内存单位的数据相互换,由于只有一条指令,包管了原子性,纵然是多处理惩罚器平台,访问内存的 总线周期也有先后,一个处理惩罚器上的互换指令实行时另一个处理惩罚器的互换指令只能等候总线周期
把lock和unlock的伪代码改一下:
留意:
- 寄存器不即是寄存器的内容(寄存器现实上许多时间,得到的数据是上下文数据)
- 线程在实行的时间,将共享资源,加载到CPU寄存器的本质:把数据的内容,变成本身的上下文(发生线程切换,上下文数据必要被生存回线程,从而变成线程独有的资源 ---- 线程局部储存)
12. 可重入和 线程安全
(一)概念
- 线程安全:多个线程并发同一段代码时,不会出现差别的结果。常见对全局变量大概静态变量举行利用,而且没有锁掩护的环境下,会出现该标题
- 重入:同一个函数被差别的实行流调用,当前一个流程还没有实行完,就有其他的实行流再次进入,我们称之为重入。一个函数在重入的环境下,运行结果不会出现任何差别大概任何标题,则该函数被称为可重入函数,否则,是不可重入函数。
(二)常见的线程不安全的环境
- 不掩护共享变量的函数
- 函数状态随着被调用,状态发生变革的函数
- 返回指向静态变量指针的函数
- 调用线程不安全函数的函数
(三)常见的线程安全的环境
- 每个线程对全局变量大概静态变量只有读取的权限,而没有写入的权限,一样寻常来说这些线程是安全的
- 类大概接口对于线程来说都是原子利用
- 多个线程之间的切换不会导致该接口的实行结果存在二义性
(四)常见不可重入的环境
- 调用了malloc/free函数,由于malloc函数是用全局链表来管理堆的
- 调用了尺度I/O库函数,尺度I/O库的许多实现都以不可重入的方式利用全局数据结构
- 可重入函数体内利用了静态的数据结构
(五)常见可重入的环境
- 不利用全局变量或静态变量
- 不利用用malloc大概new开发出的空间
- 不调用不可重入函数
- 不返回静态或全局数据,全部数据都有函数的调用者提供
- 利用当地数据,大概通过制作全局数据的当地拷贝来掩护全局数据
(六)可重入与线程安全接洽
- 函数是可重入的,那就是线程安全的
- 函数是不可重入的,那就不能由多个线程利用,有大概引发线程安全标题
- 如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的
(七)可重入与线程安全区别
- 可重入函数是线程安全函数的一种
- 线程安全不愿定是可重入的,而可重入函数则肯定是线程安全的。
- 如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未开释则会产存亡锁,因此是不可重入的
|