目录
一、进程锁
锁在IT界都是非常重要的,不单在Python中出现,尤其是数据库中的锁更多,比如:表锁、行锁、悲观锁、乐观锁、进程锁、互斥锁、递归锁、可重入锁、死锁等。
使用锁的目的就是为了保证安全!
加锁一定好吗? 虽然保证了数据的安全,但是呢,执行的效率一定是降低了。有些场景需要加锁的时候一定要加锁。
import time
from multiprocessing import Process, Lock
def task(i, lock):
# 上一把锁
lock.acquire()
print("进程%s来了" % i)
time.sleep(1)
print("进程%s走了" % i)
# 释放锁
lock.release()
"""只要你上了锁,一定不要忘了最后释放锁,否则的话,别的进程永远进不来"""
if __name__ == '__main__':
lock = Lock() # 得到一把锁
for i in range(3):
p = Process(target=task, args=(i + 1, lock))
p.start()
二、如何查看进程号
有了进程号,我们就可以通过进程号来结束进程的执行 ------> kill 9176 kill -9 9176
import time
import os
from multiprocessing import Process, Lock
def task():
print("task进程的进程号:", os.getpid()) # os.getpid()
# 写在哪个进程里面就会输出哪个进程的进程号
print("task进程的父进程的进程号:", os.getppid()) # parent process
import time
time.sleep(20)
if __name__ == '__main__':
p = Process(target=task, )
p.start()
print('子进程的进程号:', p.pid)
print("主进程的进程号", os.getpid())
time.sleep(10)
三、进程之间的数据隔离问题
n = 100
def task():
global n
n = 1
print("子进程")
from multiprocessing import Process
"""这两个进程之间的数据有没有通信? 没有通信"""
if __name__ == '__main__':
p = Process(target=task)
p.start()
"""先让子进程先执行,让子进程去改值"""
p.join()
print("主进程中得值:", n)
四、队列
4.1 概念介绍——multiprocess.Queue
创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。 Queue([maxsize])创建共享的进程队列。队列的特点:先进先出
参数 :maxsize是队列中允许的最大项数。如果省略此参数,则无大小限制。
底层队列使用管道和锁定实现。
4.2 方法介绍
Queue的实例q具有以下方法:
- q.get( [ block [ ,timeout ] ] ):返回q中的一个项目。如果q为空,此方法将阻塞,直到队列中有项目可用为止。block用于控制阻塞行为,默认为True. 如果设置为False,将引发Queue.Empty异常(定义在Queue模块中)。timeout是可选超时时间,用在阻塞模式中。如果在制定的时间间隔内没有项目变为可用,将引发Queue.Empty异常。
- q.put(item [, block [,timeout ] ] ) :将item放入队列。如果队列已满,此方法将阻塞至有空间可用为止。block控制阻塞行为,默认为True。如果设置为False,将引发Queue.Empty异常(定义在Queue库模块中)。timeout指定在阻塞模式中等待可用空间的时间长短。超时后将引发Queue.Full异常。
- q.qsize() :返回队列中目前项目的正确数量。此函数的结果并不可靠,因为在返回结果和在稍后程序中使用结果之间,队列中可能添加或删除了项目。在某些系统上,此方法可能引发NotImplementedError异常。
- q.empty() :如果调用此方法时 q为空,返回True。如果其他进程或线程正在往队列中添加项目,结果是不可靠的。也就是说,在返回和使用结果之间,队列中可能已经加入新的项目。
- q.full() :如果q已满,返回为True。由于线程的存在,结果也可能是不可靠的(参考q.empty()方法)。
4.3 在Python中如何使用队列
它给我们提供了一个内置的队列类
from multiprocessing import Queue
q = Queue(3)
# 入队:往队列里面添加数据
"""block=True, timeout=None"""
q.put('helloworld1')
q.put('helloworld2')
q.put('helloworld3')
'block=False: 如果往队列里面放数据放不进去的时候,
会立马报错,报队列已满的错误信息'
q.put('helloworld4', block=False)
'timeout:超时的时间,如果在指定的时间内,没有放进去,就会直接报错'
q.put('helloworld4', timeout=3)
q.put_nowait('helloworld4')
# 出队:从队列里面取出值
print(q.get())
print(q.get())
print(q.get())
# print(q.get(block=False))
# print(q.get(timeout=3))
# print(q.get_nowait())
print(q.qsize())
print(q.empty())
print(q.full())
"""现在队列里面的数据在哪里存着? 在内存中存着"""
补充:
专业的消息队列:kafka, rabbitmq等专业的消息队列,他们能够解决一些特殊场景的问题 。
五、生产者和消费者模型
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
5.1 为什么要使用生产者和消费者模式
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
5.2 什么是生产者消费者模式
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
5.3 基于队列实现生产者消费者模型
def producer(q):
"""生产10个包子"""
for i in range(10):
q.put("生产的第%s个包子" % i)
def consumer(q):
while True:
res = q.get()
print(res)
if res is None:
break
from multiprocessing import Process, Queue
if __name__ == '__main__':
q = Queue(20)
# 生产者
p = Process(target=producer, args=(q,))
p.start()
# 消费者
c = Process(target=consumer, args=(q,))
c.start()
p.join()
q.put(None) # 主进程在生产者生产完毕后发送结束信号None
六、线程
6.1 有了进程为什么要有线程
进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。很多人就不理解了,既然进程这么优秀,为什么还要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的,主要体现在两点上:
- 进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
- 进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
如果这两个缺点理解比较困难的话,举个现实的例子就清楚了:如果把我们上课的过程看成一个进程的话,那么我们要做的是耳朵听老师讲课,手上还要记笔记,脑子还要思考问题,这样才能高效的完成听课的任务。而如果只提供进程这个机制的话,上面这三件事将不能同时执行,同一时间只能做一件事,听的时候就不能记笔记,也不能用脑子思考,这是其一;如果老师在黑板上写演算过程,我们开始记笔记,而老师突然有一步推不下去了,阻塞住了,他在那边思考着,而我们呢,也不能干其他事,即使你想趁此时思考一下刚才没听懂的一个问题都不行,这是其二。
现在我们应该明白了进程的缺陷了,而解决的办法很简单,我们完全可以让听、写、思三个独立的过程,并行起来,这样很明显可以提高听课的效率。而实际的操作系统中,也同样引入了这种类似的机制——线程。
6.2 线程
在一个进程中,线程就是必须存在的,至少要有一个线程来执行任务。
一个进程中可以有多个线程,在一个进程中可有开启多个线程来执行任务。
进程和线程都是由操作系统调度的。进程是操作系统分配资源的基本单位,线程是操作系统执行的最小单位。
6.3 通过threading.Thread类创建线程
import time
def task(a, b):
print("from task")
time.sleep(2)
print("aaa")
from threading import Thread
import threading
if __name__ == '__main__':
"""开线程的资源非常小,以至于代码走到这一行就立马开起来了,所以就会立刻执行"""
t = Thread(target=task, name='Thread-2', args=('a',), kwargs={'b': 1})
t.start()
"""一个进程中如果只有一个线程,该线程称之为是主线程,其他线程称之为是子线程"""
# t.join()
# print(t.name)
print("主线程")
6.4 Thread类的其他方法
Thread实例对象的方法:
- isAlive():返回线程是否活动的。
- getName():返回线程名。
- setName():设置线程名
threading模块提供的一些方法:
- threading.currentThread():返回当前的线程变量。
- threading.enumerate():返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
- threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
6.5 守护线程
设置守护进程:主进程结束,子进程跟着结束。
守护线程例1
from threading import Thread
import time
def sayhi(name):
time.sleep(2)
print('%s say hello' %name)
if __name__ == '__main__':
t=Thread(target=sayhi,args=('nick',))
t.setDaemon(True) # 必须在t.start()之前设置
t.start()
print('主线程')
print(t.is_alive())
'''
主线程
True
'''
守护线程例2
from threading import Threadpy
import time
def foo():
print(123)
time.sleep(1)
print("end123")
def bar():
print(456)
time.sleep(3)
print("end456")
t1 = Thread(target=foo)
t2 = Thread(target=bar)
t1.daemon = True
t1.start()
t2.start()
print("main-------")