Skip to content

Commit

Permalink
多线程编程简略完成
Browse files Browse the repository at this point in the history
  • Loading branch information
ruyuejun committed Jun 12, 2019
1 parent 44a58fd commit 8396903
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 12 deletions.
100 changes: 89 additions & 11 deletions 02-Go并发编程/00-并发简略3-多线程编程.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## 多线程编程概览
## 线程

#### 1.1 线程概述

POSIX标准定义了线程和其操作方法,从Linux2.6开始NPTL基于POSIX成为了其默认的线程实现
线程可以视为某个进程内部的控制流其包括
- 主线程随着进程的启动而创建必定拥有因为进程必须有一个控制流持续运行
- 其他线程不一定拥有由主线程或者其他线程创建调用pthread_create函数
Expand All @@ -13,23 +14,100 @@ POSIX标准定义了线程和其操作方法,从Linux2.6开始,NPTL(基于

线程自己私有的数据都存储在线程栈中线程栈位于所属进程的虚拟内存地址中不过一个进程的很多资源会被所有线程共享比如代码段数据段信号处理函数当前进程持有的文件描述符等所以同一进程中的多个线程运行的一定是同一个程序只不过具体的控制流和执行的函数可能不同也正因如此同一进程内的多线程共享数据变得很轻松创建新线程也无需再复制资源了

## 认识线程

#### 2.1 线程标识
#### 1.2 线程标识

每个线程也有属于自己的ID称为TID只在其所属的进程范围内唯一

贴士Linux中的线程ID在系统范围内也是唯一的且线程不存在后该ID可被其他线程复用
注意Linux中的线程ID在系统范围内也是唯一的且线程不存在后该ID可被其他线程复用

#### 1.3 线程终止与分离

线程可以通过多种方式终止同一进程中的其他线程常用方式是调用pthread_cancel该函数执行后立即返回不会等待线程做出的响应结果

如果一个线程可以被连接其终止时必须连接否则会成为僵尸线程这样会导致资源浪费如何连接处于终止中的线程呢使用pthread_join函数执行

默认情况下线程都是可以被连接的但是将线程分离后pthread_detach),则线程不可被连接且内核将在线程终止时执行清理和销毁分离操作不可逆

#### 1.4 线程调度

进程之间存在着树形关系线程不同任何线程都可以对同一进程的其他线程进行有限的管理

调度器会把事件划分为极小的时间片并把这些时间片分配给不同的线程以使众多线程都有机会在CPU上运行也造成了我们多线程被并行运行的幻觉

## 线程同步-互斥方式

#### 2.1 共享数据一致性

一个程序一般包含多个线程线程间往往通过共享数据的方式传递数据共享数据大多存放在共享内存中在多线程开发中我们总是设法保证共享数据的一致性除非该数据永远只被一个线程访问

保证共享数据一致性的最简单和最彻底的方法就是使该数据编程一个不变量在编程语言中可以使用常量来确保但是类似计数器这样的数据变成常量是不现实的必须通过额外手段来保证被多个线程共享变量的一致性这就有了临界区的概念

临界区是只能被串行化访问或执行的某个资源或某段代码常称为串行区域一般通过同步机制来保证临界区有效常见的同步机制有原子操作互斥量条件变量等

#### 2.2 互斥量

互斥metex):在同一时刻只允许一个线程处于临界区内

互斥量线程将对象锁定后才能进入临界区否则线程就会阻塞这个对象我们称之为互斥对象或者互斥量

由此可知互斥量有已锁定未锁定两种状态且一旦被锁则不能再次锁定只有解锁后才能再次锁定即不允许别的线程二次加锁)。多个线程为了能够访问临界区将会争夺锁的所有权

线程在离开临界区的时候必须对互斥量进行解锁此时其他想进入该临界区的线程将会被唤醒再次争夺锁

在一般情况下应该尽量少的使用互斥量每个互斥量保护的临界区应该在合理范围内并晶亮大如果多个线程频繁出入某个较大的临界区且经常存在访问冲突应该把该临界区切分为若干较小的临界区并使用不同的互斥量加以保护这样能降低线程被阻塞的概率提升性能

#### 2.3 死锁

尽量不要让不同的互斥量锁保护的临界区重叠不同的互斥量保护的临界区中包含了对同一个共享资源的同一种操作此时会产生死锁

解决死锁的办法有两种
- 试锁定-回退操作系的线程库中提供了该功能在执行一个代码块时需要先后锁定多个互斥量成功锁定其中一个互斥量后应该使用试锁定的方法来锁定后续一个互斥量如果后续任一互斥量锁定失败则解锁刚才被锁的互斥量重新进行争夺锁尝试注意多个互斥量被成功加锁后解锁顺序和加锁顺序相反这样可以减少回退次数
- 固定顺序锁定举例线程A和线程B总是先锁定互斥量1再锁定互斥量2那么就不会产生死锁

第一种方案更加有效但是程序变得复杂了后一种方法简单实用但是因为存在固定顺序降低了程序的灵活性

## 线程同步-条件变量方式


条件变量不从临界区的线程访问数量上入手而是在对应的共享数据状态发生变化时通知其他被阻塞线程条件变量一般与互斥量组合使用

互斥量有时候也不能完美解决问题比如
```
有一个数据队列,存在着生产者线程、消费者线程对该队列数据的使用,使用互斥量保护该数据队列,生产者线程添加数据,消费者线程消费数据。
如果生产者线程获得互斥量,发现数据队列已满,无法添加新数据,这时生产者线程可能就会在临界区等待,直到有空闲区间,这种做法明显是错误的,应该在发现没有空闲区间时直接解锁退出。
同样消费者线程获取锁后,发现数据队列为空,则也会一直等待,我们也推荐发现为空后直接解锁。
```

条件变量和互斥量配合可以解决上述问题条件变量有三种操作
- 等待通知wait):如果当前数据状态不满足条件则解锁与该条件变量绑定在一起的互斥量然后阻塞当前线程直到收到该条件变量发来的通知
- 单发通知signal):让条件变量向至少一个正在等待它通知的线程发送通知以表示共享数据状态发生了改变
- 广播通知broadcast):给等待通知的所有线程发送通知



#### 多进程与多线程利弊

交换数据多进程交换数据方式复杂管道消息队列信号量共享内存),多线程之间交换数据很简单但会产生竞态条件需要解决同步问题

综合而言多线程方式具备大量优势但是在处理信号同时运行多套不同程序以及包含多个需要超大内存支持的任务等多进程方式更适合

#### 2.2 线程调度
#### 多核CPU并发编程

调度器会把事件划分为极小的时间片并把这些时间片分配给不同的线程以使众多线程都有机会在CPU上运行也造成了我们多线程被并行运行的幻觉
在单核CPU上每一时刻只有1个线程在运行而在多核CPU上操作系统会对CPU进行调度会让人感觉有多个线程在同时运行这时只能算是并发运行

## 线程同步
- 并发运行多个任务同时被发起但同一时刻这些任务不一定都处于运行状态
- 并行运行同一时刻有多个任务在真正的同时执行其必要天监事多核心CPU

在多线程开发中我们总是设法保证共享数据的一致性除非该数据永远只被一个线程访问
其实增加CPU核心数并发程序的运行速度提升曲线会随着CPU数量的增加而逐渐趋于平缓增加因为这种趋于平缓的态势取决于程序中使用同步方法的数量和执行耗时越多则平滑态势越明显

保证共享数据一致性的最简单和最彻底的方法就是使该数据编程一个不变量编程语言中使用常量来确保但是类似计数器这样的数据变成常量是不现实的必须通过额外手段来保证被多个线程共享变量的一致性这就有了临界区的概念
针对以上问题给出的开发建议如下
- 控制临界区纯度尽量不要把无关代码尤其是I/O操作包括打印函数放置在临界区
- 控制临界区粒度如果存在相邻的多个临界区内部都是操作同一个共享数据代码可以合并他们
- 降低临界区代码耗时比如使用合理的算法
- 避免长时间持有互斥量比如使用条件变量
- 优先使用原子操作而不是互斥量因为原子操作是直接利用硬件级原语保证并发安全



Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
- [《Go语言从入门到进阶实战》](https://book.douban.com/subject/30240200/):语法基础,入门力荐,但此书比较冷门
- [《Go语言圣经》](https://github.com/gopl-zh/gopl-zh.github.com):中文翻译较差,推荐购买原版英文书籍
- [《GoWeb编程》](https://book.douban.com/subject/27204133/):Go语言Web编程基础
- [《Go并发编程》](https://book.douban.com/subject/27016236/):Go并发模型介绍的好书,但该书语句组织极度欠佳,推荐读直接阅读本笔记`02-Go并发编程`
- [《Go语言学习笔记》](https://book.douban.com/subject/26832468/):言简意赅,深入浅出,力荐

#### Go语言资料
Expand Down

0 comments on commit 8396903

Please sign in to comment.