进程
同步:
多任务, 多个任务之间执行的时候要求有先后顺序,必须一个先执行完成之后,
另一个才能继续执行, 只有一个主线
异步,指的是:多个任务之间执行没有先后顺序,可以同时运行,
执行的先后顺序不会有什么影响,存在的多条运行主线
对于上篇计算错误的问题,可以通过线程同步来进行解决
思路,如下:
系统调用t1,然后获取到g_num的值为0,此时上一把锁,即不允许其他线程操作g_num
t1对g_num的值进行+1
t1解锁,此时g_num的值为1,其他的线程就可以使用g_num了,而且是g_num的值不是0而是1
同理其他线程在对g_num进行修改时,都要先上锁,处理完后再解锁,在上锁的整个过程中不允许其他
线程访问,就保证了数据的正确性
互斥锁
当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制
某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;
直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。
互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
threading模块中定义了Lock类,可以方便的处理锁定:
# 创建锁
mutex = threading.Lock()
# 锁定
mutex.acquire()
# 释放
mutex.release()
上锁解锁过程
当一个线程调用锁的acquire()方法获得锁时,锁就进入“locked”状态。
每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“阻塞”,
直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入“unlocked”状态。
线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。
注意:
1、互斥锁,所谓互斥就是竞争资源的多方必须:都加锁、加同一把锁 才有意义
2、一般情况下,只针对最紧迫的资源加锁(范围越小越好)
总结
锁的好处:
确保了某段关键代码只能由一个线程从头到尾完整地执行
锁的坏处:
阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁。
进程概念
进程是资源分配的最小单位,程序隔离的边界。
CPU的时间片轮转,在不同的时间段切换执行不同的进程,但是切换进程是比较耗时的;就引来了轻量级进程,也就是所谓的线程,
一个进程中包括多个线程(代码流,其实也就是进程中同时跑的多个方法体)
程序:例如xxx.py这是程序,是一个静态的
进程:一个程序运行起来后,代码+用到的资源 称之为进程,它是操作系统分配资源的基本单元。
进程的状态
工作中,任务数往往大于cpu的核数,即一定有一些任务正在执行,而另外一些任务在等待cpu进行执行,
因此导致了有了不同的状态
就绪态:运行的条件都已经慢去,正在等在cpu执行
执行态:cpu正在执行其功能
等待态:等待某些条件满足,例如一个程序sleep了,此时就处于等待态
进程的创建-multiprocessing
multiprocessing模块就是跨平台版本的多进程模块,提供了一个Process类来代表一个进程对象,这个对象可以理解为是一个独立的进程,可以执行另外的事情
import multiprocessing
if __name__ == '__main__':
# 创建进程
# 1. 导入 multiprocessing 模块
# 2. multiprocessing.Process() 创建子进程
# 3. start() 方法启动进程
p1 = multiprocessing.Process(group=None, target=work1)
p1.start()
#进程间不共享全局变量
# 导入创建进程需要的代码
import multiprocessing
import time
#定义函数
def work1():
for i in range(10):
print("---work%d----"%i)
time.sleep(0.5)
#打印当前进程的PID
print("当前进程的pid",multiprocessing.current_process().pid)
if __name__ == '__main__':
#创建进程
t1 = multiprocessing.Process(group=None,target=work1)
t1.start()
进程、线程对比
功能
进程,能够完成多任务,比如 在一台电脑上能够同时运行多个QQ
线程,能够完成多任务,比如 一个QQ中的多个聊天窗口
定义的不同
进程是系统进行资源分配和调度的一个独立单位.
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.
线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),
但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
区别
一个程序至少有一个进程,一个进程至少有一个线程.
线程的划分尺度小于进程(资源比进程少),使得多线程程序的并发性高。
进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率
线程不能够独立执行,必须依存在进程中
优缺点
线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。
Queue介绍
可以使用multiprocessing模块的Queue实现多进程之间的数据传递,
Queue本身是一个消息列队程序
Queue基本使用
创建队列并且放入值
说明
初始化Queue()对象时(例如:q=Queue()),若括号中没有指定最大可接收的消息数量,或数量为负值,那么就代表可接受的消息数量没有上限(直到内存的尽头);
Queue.qsize():返回当前队列包含的消息数量;
Queue.empty():如果队列为空,返回True,反之False ;
Queue.full():如果队列满了,返回True,反之False;
Queue.get([block[, timeout]]):获取队列中的一条消息,然后将其从列队中移除,block默认值为True;
如果block使用默认值,且没有设置timeout(单位秒),消息列队如果为空,此时程序将被阻塞(停在读取状态),直到从消息列队读到消息为止,如果设置了timeout,则会等待timeout秒,若还没读取到任何消息,则抛出"Queue.Empty"异常;
如果block值为False,消息列队如果为空,则会立刻抛出"Queue.Empty"异常;
Queue.get_nowait():相当Queue.get(False);
Queue.put(item,[block[, timeout]]):将item消息写入队列,block默认值为True;
如果block使用默认值,且没有设置timeout(单位秒),消息列队如果已经没有空间可写入,此时程序将被阻塞(停在写入状态),直到从消息列队腾出空间为止,如果设置了timeout,则会等待timeout秒,若还没空间,则抛出"Queue.Full"异常;
如果block值为False,消息列队如果没有空间可写入,则会立刻抛出"Queue.Full"异常;
Queue.put_nowait(item):相当Queue.put(item, False);
消息数量、判断是否为空、判断是否已满
# 导入模块
import multiprocessing
# 定义消息队列
# 如果不指定队列长度,则默认为最大,如果指定了消息队列的大小,则消息队列就有上限控制
# 此处的 Queue(3) 指的是放入3条消息
queue = multiprocessing.Queue(3)
# queue.put(值) 向消息队列中放入内容
# put的值几乎可以是任意类型
queue.put(1) # 放入第一个值
queue.put("hello") # 放入第二个值
queue.put([1, 2, 3]) # 放入第三个值
# 打印队列对象
print(queue)
# 获取第一个值
value1 = queue.get()
print(value1)
# 获取第二个值
value2 = queue.get()
print(value2)
# 获取第三个值
value3 = queue.get()
print(value3)
Queue实现进程间通信
我们以Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据:
import multiprocessing
import time
def write_queue(queue):
# 循环写入数据
for i in range(10):
if queue.full():
print("队列已满!")
break
# 向队列中放入消息
queue.put(i)
time.sleep(0.5)
def read_queue(queue):
# 循环读取队列消息
while True:
# 队列为空,停止读取
if queue.empty():
print("---队列已空---")
break
# 读取消息并输出
result = queue.get()
print(result)
if __name__ == '__main__':
# 创建消息队列
queue = multiprocessing.Queue(3)
# 创建子进程
p1 = multiprocessing.Process(target=write_queue, args=(queue,))
p1.start()
# 等待p1写数据进程执行结束后,再往下执行
p1.join()
p1 = multiprocessing.Process(target=read_queue, args=(queue,))
p1.start()