1.进程
进程:计算机中一个程序在一个数据集上一次动态执行过程,主要包含三部分内容
⚫ 程序:描述进程的功能以及处理流程
⚫ 数据集:功能处理过程中需要的资源数据
⚫ 进程控制:严格控制进程执行过程中的各种状态
一个软件程序要运行,需要将软件依赖的数据加载到内存中,通过 CPU 进行运算并按照程 序定义的逻辑结构进行流程控制,知道数据处理完成后程序退出! 在程序实际执行过程中,进程只是分配需要的数据资源,是程序的主体,在程序运行时真正 运行的是线程,每个进程至少会有一个线程。
2.线程(Thread)
计算机中程序运行的实际执行者就是线程,线程又称为轻量级进程,是一个 CPU 的执行单 元,每个进程至少会有一个主线程用于执行程序
线程和进程对比如下
⚫ 一个进程可以有多个线程,但是至少有一个主线程
⚫ 一个线程只能属于一个进程
⚫ 一个进程中多个线程,可以共享进程中提供的数据
⚫ CPU 运算分配给线程,CPU 上执行运算的是线程
⚫ 线程是最小的运行单元,进程是最小的资源管理单元
2.1 串行、并行、并发
什么是并发?
所谓并发:表面上是同时执行,并发是相对于顺序执行的概念
并发:在很短的时间之类,多个任务切换执行,在一个时间段~体现出来一种几个任务同时执行的表象,并发,在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,担任一个时刻点只有一个程序在处理机上运行。
并行:(以可同时执行的计算指令方式编程)
串行:串行程序中,程序会按照顺序执行每一条指令,在整个程序运行过程中,仅存在一个运行上下文。(即一个调用栈,一个堆)
串行:就是传统意义上的同步、顺序的意思,按照一定的执行步骤顺序执行每个环节
并行:就是传统意义上的异步、同时的意思,同时执行接受到的多个任务
并发:同时接收到多个任务,同时执行多个任务,但是具体到某个时刻~只是在执行一个任 务,只是在很短时间内在多个任务之间切换,模拟形成了多个任务同时执行的现象 而大部分计算机中的应用程序的执行,一般都是并发执行机制的多任务处理机制
2.2多线程编程
PYTHON 本身对于多线程并发机制的支持是比较完善的
在 PYTHON2 中提供了标准模块 thread 和 threading 支持多线程的并发编程 但是随着并发编程的实际使用操作过程,thread 模块过于底层的控制方式对于并发编程的 新手来说挺不是非常友好,要求多线程的程序开发逻辑思维清晰同时又具备大量开发经验的 情况下,可以控制的非常精细。
在PYTHON3 中将 thread 模块进行了规范内置,更名为_thread,友好的提醒如果你不是并发编 程的骨灰级爱好者,请不要轻易尝试使用_thread 进行操作,而是推荐使用操作更加灵活使 用更加简洁的 threading 模块进行并发编程的处理。
2.2.1 PYTHON 中的多线程
官方推荐的_threading 模块的多线程并发编程机制,结合时下流行的面向过程/面向对象的 编程处理模式,主要有两种操作方式
⚫ 函数式的线程创建方式,适合面向过程程序的并发编程实现
⚫ 面向对象的创建方式,适合面向对象程序的并发编程实现
threading 模块属性和方法
名称 | 描述 |
---|---|
Thread | 线程类,用于创建和管理线程 |
Event | 事件类,用于线程同步 |
Condition | 条件类,用于线程同步 |
Lock/RLock | 锁类,用于线程同步 |
Timer | 延时线程,用于在一定事件后执行一个函数 |
Semaphore/BoundedSemaphore | 信号量类,用于线程同步 |
active_count()/activeCount() | 获取当前 alive 状态的所有线程数量 current |
current_thread()/currentThread() | 获取当期正在执行的线程对象 |
get_ident() | 获取运行中程序当前线程的唯一编号 |
enumerate() | 获取所有 alive 状态线程列表 |
local | 线程局部数据类 |
stack_size([size]) | 获取线程占用内存栈的大小 |
main_thread() | 获取主线程 |
2.2.2Thread 类型属性和方法
名称 | 描述 |
---|---|
init(group,target,name,args,kwargs) | 构造方法,创建线程类型 |
is_alive()/isAlive() | 判断当前线程是否 alive 状态 |
run() | 线程执行方法,自定义线程必须重写该函数 |
start() | 线程启动方法 |
join([timeout=None]) | 线程独占,等待当前线程运行结束或者超时 |
ident | 标识当前线程的唯一编号 name 当前线程名称 |
daemon | 布尔值,判断当前线程是否守护线程 |
⚫多线程售票:相当于多个窗口同时售票(简单)
代码:
"""
desc 多线程并发
version 1.1.0
author lkk
email [email protected]
"""
import threading, time
ticket = 10
def sale_ticket():
global ticket
while ticket > 0:
print(threading.current_thread().name, '{}号票已经售出'.format(ticket))
ticket -= 1
time.sleep(1)
if __name__ == '__main__':
S1 = threading.Thread(target=sale_ticket, name='窗口1')
S2 = threading.Thread(target=sale_ticket, name='窗口2')
S1.start()
S2.start()
start = time.clock()
elapsed = (time.clock() - start)
print("程序耗时:", elapsed)
程序运行要比单线程程序运行效率大大提高
2.2.3线程状态
join:线程的 join 状态是独占模式,当前线程独占 CPU 运行单元,必须等待当前线程执行完 成或者超时之后,才能运行其他线程 。
"""
desc 线程状态join
version 1.1.0
author lkk
email [email protected]
"""
# 引入模块
import threading, time
count = 10
def sale_ticket():
global count
while count > 0:
print(threading.current_thread().getName(), "售出一张票:", count)
count -= 1
time.sleep(0.5)
else:
print(threading.current_thread().getName(), "没有票了")
if __name__ == "__main__":
# 定义多个线程 ( 窗口 )
t1 = threading.Thread(name="窗口 1", target=sale_ticket)
t2 = threading.Thread(name="窗口 2", target=sale_ticket)
t3 = threading.Thread(name="窗口 3", target=sale_ticket)
t4 = threading.Thread(name="窗口 4", target=sale_ticket)
t5 = threading.Thread(name="窗口 5", target=sale_ticket)
# 启动五个窗口同时售票
t1.start()
# 线程 t1 调用 join ,独占模式运行,等待 t1 线程运行结束或者超时,才能继续运行其他线程
t1.join()
t2.start()
t3.start()
t4.start()
t5.start()
2.2.4线程管理-锁[Lock/RLock]
多线程程序在运行过程中,由于多个线程访问的是同一部分数据,很容易会造成共享数据访 问冲突的现象,如果一旦出现冲突程序就会出现执行结果不符合期望的结果
修改售票程序,两个窗口同时售票,修改代码如下
"""
desc 多线程并发
version 1.1.0
author lkk
email [email protected]
"""
import threading, time,os,sys
ticket = 10
def sale_ticket():
global ticket
while 1:
if lock.acquire():
if ticket > 0:
print(threading.current_thread().name, '{}号票已经售出'.format(ticket))
time.sleep(0.2)
ticket -= 1
else:
print(threading.current_thread().name, '票已经售空')
lock.release()
if __name__ == '__main__':
lock = threading.Lock()
S1 = threading.Thread(target=sale_ticket, name='窗口1')
S2 = threading.Thread(target=sale_ticket, name='窗口2')
S1.start()
S2.start()
此时共享数据的修改操作,在多线程的情况下,是需要通过锁定的方式进行独占修改的! 就如同如厕一样,当多个人[线程]在执行程序,修改数据[如厕]时,每个线程在操作过程中都 需要锁定这一部分数据[厕所],直到数据处理完成之后解锁,下一个线程才能进行操作
python 中提供了两种线程锁的操作
⚫ 同步锁/互斥锁:Lock
⚫ 可重用锁:RLock
锁的操作主要是获取锁和释放锁两种
⚫ acquire() 获取锁,上锁,锁定
⚫ release() 释放锁,开锁,解锁
2.2.5线程管理-死锁[Dead Lock]
线程锁固然功能强大,可以管理多个线程之间的共享数据问题 但是同时它的强大也带来了比较纠结的问题,需要开发人员对于锁定的数据有一个良好的认 知,否则特别容易造成死锁的现象,比较著名的哲学家吃饭问题就是死锁的典型代表
由于计算机运算速度较快,所以有两种方案可以将问题放大
⚫ 给执行函数添加休眠时间
⚫ 添加线程数量
死锁并不是每次都会出现的,而是程序在执行过程中,根据系统 CPU 时间片的切换机制恰 好遇到了重复上锁的情况,就会死锁
实际项目开发过程中,一定要注意死锁情况的影响 这样的情况可以通过可重用锁 RLock 进行锁定处理!
PYTHON 中的互斥锁,只有两种状态,locked 和 unlocked,如果一旦重复上锁就会死锁 但是可重用锁 RLock,在锁定的基础上提供了一个计数器 counter,可以计算上锁的次数然 后通过 release()解锁时就会重新运算计数器,等待计数器清零时所有锁全都释放了
import threading, time
def zhexuejia1():
if lock1.acquire():
if lock2.acquire():
print("你给我叉子,我就给你刀子")
lock2.release()
lock1.release()
def zhexuejia2():
if lock2.acquire():
if lock1.acquire():
print("你给我刀子,我就给你叉子")
lock1.release()
lock2.release()
if __name__ == "__main__":
lock1 = threading.Lock()
lock2 = threading.Lock()
for i in range(1000):
zxj = threading.Thread(name="哲学家 A" + str(i), target=zhexuejia1)
zxj2 = threading.Thread(name="哲学家 B" + str(i), target=zhexuejia2)
zxj.start()
zxj2.start()