1.锁

lock.acquire()# 上锁
lock.release()# 解锁

#同一时间允许一个进程上一把锁 就是Lock
加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲速度却保证了数据安全。
#同一时间允许多个进程上多把锁 就是[信号量Semaphore]
信号量是锁的变形: 实际实现是 计数器 + 锁,同时允许多个进程上锁

# 互斥锁Lock : 互斥锁就是进程的互相排斥,谁先抢到资源,谁就上锁改资源内容,为了保证数据的同步性
# 注意:多个锁一起上,不开锁,会造成死锁.上锁和解锁是一对.


"""
互斥锁lock:互斥锁就是进程的相互排斥
谁先抢到资源,谁就上锁改资源内容,为了保证数据的同步性
# 注意:多个锁一起上,不开锁,会造成死锁。上锁和解锁是一对
"""
from multiprocessing import Process,Lock
import time 
# 创建一把锁 
lock = lock()
# 上锁
lock.acquire()
print("111")
lock.release()

# 死锁:只上锁,不解锁,会阻塞,产生死锁
lock.acquire()
# lock.release()
lock,release()
# lock.release()
lock.acquire()
# lock.release()
print("222")

# 111
# 222



例子:



"""
比如12306的抢票软件,实际上是把mysql数据库中的内容读出来先与存在redis的内容中,比如内存
的读写的速度极快,不需要再刻意的加延迟等待,既可以把该有的数据更新或者返回。
等待抢票的高峰期过了,在慢慢把内存中的数据更新会Mysql当中
"""



# (1) 模拟抢票软件
import json
# 读取票数,更新票数
def wr_info(sign,dic=None):
    if sign == "r":
        with open("ticket",mode="r",encoding="utf-8") as fp:
            dic = json.load(fp)
        return dic
    elif sign == "w":
        with open("ticket",mode="w",encoding="utf-8") as fp:
            json.dump(dic,fp)
            
# 抢票方法
def get_ticket(person):
    # 读取数据库的实际票数
    dic = wr_info("r")
    # with open("ticket",mode="r",encoding="utf-8") as fp:
        # dic = json.load(fp)
    time.sleep(0.1)
    if dic["count"] > 0:
        print("%s抢到票了" % (person) )
        dic["count"] -= 1
        # 更新数据库
        wr_info("w",dic)
        # with open("ticket",mode="w",encoding="utf-8") as fp:
            # json.dump(dic,fp)
    else:
        print("%s没有买到这个票" % (person))

# 用ticket来进行函数的统一调用
def ticket(person,lock):
    # 查询票数
    dic = wr_info("r")
    # with open("ticket",mode="r",encoding="utf-8") as fp:
        # dic = json.load(fp)
    print("%s 查询余票 : %s" % (person,dic["count"]))
    lock.acquire()
    
    # 开始抢票
    get_ticket(person)
    lock.release()

if __name__ == "__main__":
    lock = Lock()
    for i in range(10):
        p = Process(target=ticket,args=("person%s" % (i),lock) )
        p.start()

## 思路:1.写一个查票和更新票的方法 def wr_info(sign,dic=None)
# 就行对数据库的操作,本例这个地方对文件就行操作,查"r"and "w".如果查询到数据之后会把数据返回回来
# 由于操作的是字典会使用json
#  2.写一个抢票的方法 def get_ticket(person)
# 先查询有没有票,如果有票就行操作字典中的数据,dic['count'] -= 1
# 把数据更新到数据库,也就是调用def wr_info("w",dic)
# 如果没有票的话就行打印消息提示用户
# 3、写一个ticket总体就行调用 def ticket(proson,lock)
# # 1.查询票数 2.开始抢票 使用lock.acquire() 和lock.acquire()就行对抢票方法中的结果就行封锁,谁先占用资源谁先用
# 4.在总程序下方写一个接口程序,就行多进程并发,抢票
# 注意Process(target=ticcket,args=("person%s" %(i),lock)
# 其中的args是传入并发进程执行的方法中的参数,注意是元祖格式
# ticket文件中的数据是 {'count':1}
# person0 查询余票 : 1
# person1 查询余票 : 1
# person2 查询余票 : 1
# person4 查询余票 : 1
# person0抢到票了
# person3 查询余票 : 0
# person1没有买到这个票
# person2没有买到这个票
# person5 查询余票 : 0
# person8 查询余票 : 0
# person7 查询余票 : 0
# person4没有买到这个票
# person6 查询余票 : 0
# person9 查询余票 : 0
# person3没有买到这个票
# person5没有买到这个票
# person8没有买到这个票
# person7没有买到这个票
# person6没有买到这个票
# person9没有买到这个票



# (2)区分同步和异步
# 在产生进程对象的时候,进程之间是异步 上锁之后 进程之间变成同步



def func(num,lock):
    lock.acquire()
    print("走到上锁这个地方,变成一个同步程序,先来的进程先执行,后来的进程后执行,按次序依次执行")
    print(num)
    lock.release()
    
if __name__ == "__main__":
    # lock互斥锁 , 进程之间数据不共享,
    # 但是lock对象底层是通过socket来互相发送数据,不管多少个进程,都是同一个lock锁.
    lock = Lock()
    for i in range(10):
        p = Process(target=func,args=(i,lock))
        # 1.10个子进程异步执行,是并发操作
        p.start()

# 走到上锁这个地方,变成一个同步程序,先来的程序先执行,后来的进程后执行,按照次序依次执行
# 1
# 走到上锁这个地方,变成一个同步程序,先来的程序先执行,后来的进程后执行,按照次序依次执行
# 0
# 走到上锁这个地方,变成一个同步程序,先来的程序先执行,后来的进程后执行,按照次序依次执行
# 2
# 走到上锁这个地方,变成一个同步程序,先来的程序先执行,后来的进程后执行,按照次序依次执行
# 3
# 走到上锁这个地方,变成一个同步程序,先来的程序先执行,后来的进程后执行,按照次序依次执行
# 5
# 走到上锁这个地方,变成一个同步程序,先来的程序先执行,后来的进程后执行,按照次序依次执行
# 4
# 走到上锁这个地方,变成一个同步程序,先来的程序先执行,后来的进程后执行,按照次序依次执行
# 6
# 走到上锁这个地方,变成一个同步程序,先来的程序先执行,后来的进程后执行,按照次序依次执行
# 8
# 走到上锁这个地方,变成一个同步程序,先来的程序先执行,后来的进程后执行,按照次序依次执行
# 9
# 走到上锁这个地方,变成一个同步程序,先来的程序先执行,后来的进程后执行,按照次序依次执行
# 7



 

2.信号量

基本概念:



#同一时间允许多个进程上多把锁 就是[信号量Semaphore]
    信号量是锁的变形: 实际实现是 计数器 + 锁,同时允许多个进程上锁    

# 互斥锁Lock : 互斥锁就是进程的互相排斥,谁先抢到资源,谁就上锁改资源内容,为了保证数据的同步性
# 注意:多个锁一起上,不开锁,会造成死锁.上锁和解锁是一对.



 

### 信号量 Semaphore 本质上就是锁 支部所可以控制锁的数量



from multiprocessing import Process,Semaphore
import random,time
def ktv(person,sem):
    # 上锁
    sem.acquire()
    print("%s进入ktv唱歌"%person)
    time.sleep(random.randrange(3,8))
    print("%s走出ktv离开"%(person))

    # 解锁
    sem.release()

if __name__ == '__main__':
    # 同一时间最多不允许4个进程执行ktv任务,剩下的进程等待
    sem = Semaphore(4)
    for i in range(10):
        p = Process(target=ktv,args=("person%s"(i),sem))
        p.start()
# person1进入ktv唱歌
# person0进入ktv唱歌
# person2进入ktv唱歌
# person4进入ktv唱歌
# person1走出ktv离开
# person0走出ktv离开
# person6进入ktv唱歌
# person3进入ktv唱歌
# person2走出ktv离开
# person8进入ktv唱歌
# person4走出ktv离开
# person7进入ktv唱歌
# person3走出ktv离开
# person9进入ktv唱歌
# person8走出ktv离开
# person5进入ktv唱歌
# person7走出ktv离开
# person9走出ktv离开
# person6走出ktv离开
# person5走出ktv离开
# 小结:一次性最多只有4个人在ktv唱歌



 

3.事件

# 阻塞事件 :
e = Event()生成事件对象e
e.wait()动态给程序加阻塞 , 程序当中是否加阻塞完全取决于该对象中的is_set() [默认返回值是False]
# 如果是True 不加阻塞
# 如果是False 加阻塞

# 控制这个属性的值
# set()方法 将这个属性的值改成True
# clear()方法 将这个属性的值改成False
# is_set()方法 判断当前的属性是否为True (默认上来是False)



e = Event()
# 获取该对象中的成员属性值是True还是Flase
print(e.is_set())  # False
print(1)           # 1
# 用e.set() 把该对象的成员值变成True
e.set() # True
print(e.is_set()) # True(定义)
e.wait() # 动态的给程序增加堵塞,当前的e.is_set()是True故是非堵塞(实现)
print(2) # 2
e.clear() # 将e.is_set()的属性该为False
print(3)
e.wait() # 动态的给程序加堵塞,此时的e.is_set是Fasle,那么后面的程序就不会执行了
print(4)
print(5)

# False
# 1
# True
# 2
# 3



 

例子:



# ### 模拟红绿灯效果
def traffic_light(e):
    print("红灯亮")
    while True:
        if e.is_set():
            # 为当前绿灯等待1秒
            time.sleep(1)
            # 切换成红灯
            print("红灯亮")
            e.clear()
        else:
            # 为当前红灯等待1秒
            time.sleep(1)
            # 等完1秒之后,变成绿灯
            print("绿灯亮")
            e.set()


# 小车在遇到红灯时婷, 绿灯时行.
def car(e, i):
    if not e.is_set():
        print("car %s 在等待" % (i))
        # wait 获取的值是False , 所以阻塞
        e.wait()
    print("car %s 通行了" % (i))


# 车跑完了,但是红绿灯仍然在执行.
if __name__ == "__main__":
    e = Event()
    # 启动交通灯
    p1 = Process(target=traffic_light, args=(e,))
    p1.start()

    # 开始跑车
    for i in range(20):
        time.sleep(random.randrange(0, 2))
        p2 = Process(target=car, args=(e, i))
        p2.start()

# 红灯亮
# 绿灯亮
# car 0 通行了
# 红灯亮
# car 1 在等待
# car 2 在等待
# car 3 在等待
# car 4 在等待
# 绿灯亮
# car 3 通行了
# car 2 通行了
# car 1 通行了
# car 4 通行了
# car 5 通行了
# 红灯亮
# car 6 在等待
# 绿灯亮
# car 6 通行了
# car 7 通行了
# 红灯亮
# car 8 在等待
# car 9 在等待
# 绿灯亮
# car 8 通行了
# car 9 通行了
# car 10 通行了
# car 11 通行了
# 红灯亮
# car 12 在等待
# car 13 在等待
# 绿灯亮
# car 13 通行了
# car 12 通行了
# car 14 通行了
# 红灯亮
# car 15 在等待
# car 16 在等待
# 绿灯亮
# car 16 通行了
# car 15 通行了
# car 17 通行了
# 红灯亮
# car 18 在等待
# car 19 在等待
# 绿灯亮
# car 19 通行了
# car 18 通行了
# 红灯亮
# 绿灯亮
# 红灯亮
# 绿灯亮
  .....
优化版:



# ### 优化版:
"""
# 车跑完了,红绿灯这个进程也结束掉.
if __name__ == "__main__":
    e = Event()
    lst = []    
    
    # 启动交通灯
    p1 = Process(target=traffic_light,args = (e,))
    p1.daemon = True
    p1.start()
    
    for i in range(20):
        time.sleep(random.randrange(0,2))
        p2 = Process(target=car,args=(e,i))
        p2.start()
        lst.append(p2)
    
    # 循环等待每一辆车通过红绿灯
    for i in lst:
        i.join()
        
    print("程序彻底执行结束")
"""




4.queue队列

# IPC Inter-Process Communication
# 实现进程之间通信的两种机制:
# 管道 Pipe
# 队列 Queue

# put() 存放
# get() 获取
# get_nowait() 拿不到报异常
# put_nowait() 非阻塞版本的put
q.empty() 检测是否为空 (了解)
q.full() 检测是否已经存满 (了解)



import queue
from multiprocessing import Process,Queue

# (1)基本语法
"""先进先出"""

q = Queue()
# 1.把数据放到q队列中 put
q.put(111)
# 2.把数据从队列里面拿出来 get
# res = q.get()
# print(res)
# 3.当队列里面的值都拿出来,已经没有数据的时候,在获取会阻塞.
# res = q.get() 直接阻塞
# 4.get_nowait() 无论有没有都拿,如果拿不到,直接报错
# get_nowait 内部要依赖queue模块来实现
# 没有完全优化好,不推荐使用,想用就用put和get分别设置和获取.就可以了
"""
# res = q.get_nowait()  不推荐使用
# print(res)
# try .. except .. 捕捉异常
"""
try:
    print(q.get_nowait())
# 特指queue.Empty 这种错误,执行某些逻辑.
except queue.Empty:
    print("empty")
except:
    print("其他")



 

# (2) 可以使用queue 指定队列的长度



"""最多放3个,超过最大长度,就执行阻塞"""
q = Queue(3)
q.put(1)
q.put(2)
q.put(3)
# print(q.get())
# q.put(4) 阻塞的情况
# q.put_nowait(4) 超过队列最大长度,在存值直接报错 (不推荐使用)

# (了解 不常用 full empty)
"""
# 队列值满了返回真,不满返回假
res = q.full()
print(res)
# 判断队列中是否为空
res = q.empty()
print(res)
"""



# (3) 进程之间的通讯
def func(q):
    # 1.主进程添加的值,子进程可以通过队列拿到.
    res = q.get()
    print("我是子进程", res)
    q.put("a2222")


if __name__ == "__main__":
    q1 = Queue()
    p = Process(target=func, args=(q1,))
    p.start()

    q1.get()
    # 主进程添加数据
    q1.put("a111")
    p.join()
    # 2.子进程添加的值,主进程通过队列可以拿到
    print("主进程:", q1.get())



 

5.生产者与消费者

 

"""
# 爬虫例子:
1号进程负责获取网页中所有内容
2号进程负责匹配提取网页中的关键字

1号进程就可以看成一个生产者
2号进程就可以看成一个消费者

有时可能生产者比消费者快,反之也是一样
所以生产者和消费者为了弥补之间速度的差异化,加了一个中间的缓冲队列.

生产者和消费者模型从程序上看就是一个存放和拿取的过程.
最为理想的生产设消费者模型是两者之间的运行速度相对同步.
"""

 



from multiprocessing import Process,Queue
import random,time
# 消费者模型 [负责取值]
def consumer(q,name):
    while True:
        food = q.get()
        if food is None:
            break
        time.sleep(random.uniform(0.1,1))
        print("1. %s 吃了一个%s" % (name,food))
    
# 生产者模型 [负责存值]
def producer(q,name,food):
    for i in range(5):
        time.sleep(random.uniform(0.1,1))
        print("2. %s 生产了 %s %s" % (name,food,i))
        q.put(food+str(i))
    
if __name__ == "__main__":
    q = Queue()
    # 创建消费者进程对象
    # 1号消费者
    c1 = Process(target=consumer,args=(q,"马俊强"))
    # 如果设置守护进程,主进程结束,当前消费者模型结束,不能够保证所有数据消费完毕.是一个弊端,并不理想.
    # c1.daemon = True
    c1.start()
    # 2号消费者
    c2 = Process(target=consumer,args=(q,"境泽年"))
    c2.start()    
    
    
    # 创建生产者进程对象
    # 1号生产者
    p1 = Process(target=producer,args=(q,"张国成","方便面"))
    p1.start()
    # 2号生产者
    p2 = Process(target=producer,args=(q,"易思","大力丸"))
    p2.start()
    
    # 等待2个生产者进程执行完毕.在向下执行.
    p1.join()
    p2.join()
    """
    # 四个进程,2个生产者,2个消费者,添加一个None可以让其中一个消费者终止
    还有一个消费者,需要通过另一个生产者添加一个None值,来让第二个消费者终止
    所以添加两个None
    """
    q.put(None)
    q.put(None)
运行结果:

2. 张国成 生产了 方便面 0
2. 易思 生产了 大力丸 0
1. 马俊强 吃了一个方便面0
2. 张国成 生产了 方便面 1
1. 境泽年 吃了一个大力丸0
2. 易思 生产了 大力丸 1
1. 马俊强 吃了一个方便面1
1. 境泽年 吃了一个大力丸1
2. 张国成 生产了 方便面 2
2. 张国成 生产了 方便面 3
2. 易思 生产了 大力丸 2
2. 易思 生产了 大力丸 3
1. 境泽年 吃了一个方便面3
1. 境泽年 吃了一个大力丸2
1. 马俊强 吃了一个方便面2
2. 张国成 生产了 方便面 4
2. 易思 生产了 大力丸 4
1. 境泽年 吃了一个大力丸3
1. 马俊强 吃了一个方便面4
1. 境泽年 吃了一个大力丸4




 

6.joinablequeue

 



"""
put 存入
get 获取
task_done 与 join  通过一个中间变量统计队列元素个数
每放入一个值,队列元素个数加1
通过task_done,让当前队列的元素数量减1
最后join查找统计队列数的这个变量,如果是0,才不会添加阻塞,放行.
join判断如果队列里面还有值,默认是要加阻塞的.

用法:每次get一个数据,就执行一次 task_done(),可以让中间变量的值减1
"""



# (1) 基本用法
jq = JoinableQueue()
jq.put(11)
print(jq.get())
jq.task_done()
jq.join()
print("finish")
# 
# 11
# finish



 



# ### JoinableQueue
from multiprocessing import Process,JoinableQueue
import time,random


# (2) 优化生产者消费者模型;
# 消费者模型 [负责取值]
def consumer(q, name):
    while True:
        food = q.get()
        time.sleep(random.uniform(0.1, 1))
        print("1. %s 吃了一个%s" % (name, food))
        q.task_done()


# 生产者模型 [负责存值]
def producer(q, name, food):
    for i in range(5):
        time.sleep(random.uniform(0.1, 1))
        print("2. %s 生产了 %s %s" % (name, food, i))
        q.put(food + str(i))


if __name__ == "__main__":
    jq = JoinableQueue()
    # 创建消费者
    c1 = Process(target=consumer, args=(jq, "罗婷"))
    # 伴随主进程结束而结束
    c1.daemon = True
    c1.start()

    # 创建生产者
    p1 = Process(target=producer, args=(jq, "张国成", "香水"))
    p1.start()

    p1.join()
    # 必须等队列里面的所有数据都被清空 ,判定值为0之后才放行
    jq.join()
    print("全部结束")
    # 2. 张国成 生产了 香水 0
    # 2. 张国成 生产了 香水 1
    # 1. 罗婷 吃了一个香水0
    # 1. 罗婷 吃了一个香水1
    # 2. 张国成 生产了 香水 2
    # 2. 张国成 生产了 香水 3
    # 1. 罗婷 吃了一个香水2
    # 2. 张国成 生产了 香水 4
    # 1. 罗婷 吃了一个香水3
    # 1. 罗婷 吃了一个香水4
    # 全部结束



 



# 1.把c1创建为守护进程之后 如果是下面程序中,由于是守护进程,(目的是让
#消费者这行代码执行完之后推出循环)一旦总程序执行到最后一句的时候程序就会
# 停止,会出现 生产了很多,还没有消费完的情况
#2.基于第一种情况下 我们需要在末尾边上再次像序列中添加一个值即是程序中的q.put() =None
#这样就可以推出循环了,不要使用守护进程的方式。
# 3.但是在上面的做法中由于jq.join()方式,如果是0 才不会添加堵塞放行,所以这种方式就可以使用守护
# 进程
from multiprocessing import Process, Queue
import random, time


# 消费者模型 [负责取值]
def consumer(q, name):
    while True:
        food = q.get()
        if food is None:
            break
        time.sleep(random.uniform(0.1, 1))
        print("1. %s 吃了一个%s" % (name, food))


# 生产者模型 [负责存值]
def producer(q, name, food):
    for i in range(5):
        time.sleep(random.uniform(0.1, 1))
        print("2. %s 生产了 %s %s" % (name, food, i))
        q.put(food + str(i))


if __name__ == "__main__":
    q = Queue()
    # 创建消费者进程对象
    # 1号消费者
    c1 = Process(target=consumer, args=(q, "马俊强"))
    # 如果设置守护进程,主进程结束,当前消费者模型结束,不能够保证所有数据消费完毕.是一个弊端,并不理想.
    c1.daemon = True
    c1.start()

    # 创建生产者进程对象
    # 1号生产者
    p1 = Process(target=producer, args=(q, "张国成", "方便面"))
    p1.start()

    # 等待2个生产者进程执行完毕.在向下执行.
    p1.join()
    """
    # 四个进程,2个生产者,2个消费者,添加一个None可以让其中一个消费者终止
    还有一个消费者,需要通过另一个生产者添加一个None值,来让第二个消费者终止
    所以添加两个None
    """
    # q.put(None)
# 2. 张国成 生产了 方便面 0
# 2. 张国成 生产了 方便面 1
# 1. 马俊强 吃了一个方便面0
# 2. 张国成 生产了 方便面 2
# 2. 张国成 生产了 方便面 3
# 1. 马俊强 吃了一个方便面1
# 1. 马俊强 吃了一个方便面2
# 2. 张国成 生产了 方便面 4