并发编程,python的进程,与线程


并发编程
操作系统发展史
基于单核研究
多道技术
1.空间上的复用
多个程序公用一套计算机硬件
2.时间上的复用
切换+保存状态
例子:洗衣 烧水 做饭

切换
1.程序遇到IO操作系统会立刻剥夺走CPU的执行权限
IO:input,sleep,accept,recv...阻塞 日常生活中使用软件通常都是IO密集型

2.当你的程序长时间占用CPU的时候也会被操作系统剥夺走cpu的执行权限


进程理论
进程调度
时间片轮转法+多级反馈队列

进程三状态图
ps:程序不会立刻进入运行态 都会现在就绪态等待cpu的执行


同步异步:指的是任务的提交方式
同步:提交任务之后原地等待任务的返回结果 期间不做任何事
异步:提交任务之后立刻执行下一行代码 不等待任务的返回结果 >>> 结果的获取使用异步回调机制
阻塞非阻塞:指的是程序的运行状态
阻塞:阻塞态
非阻塞:就绪态或者是运行态


创建进程的两种方式
使用使用Process实例化
继承Process类重写run方法
ps:windows在开启进程的时候必须在__main__代码块内,因为windows是以模块导入的方式从上执行代码

什么是进程:
正在运行的程序
一个进程对应到内存中就是一块独立的内存空间

join方法
主进程等待某个指定的子进程运行结束,不影响其他子进程的运行

进程对象及其他方法
current_process().pid
os.getpid
os.getppid
terminate()
is_alive()

守护进程
daemon
这一句化必须在start之前使用

进程间数据是隔离的

互斥锁
多个程序操作用一份数据的时候会出现数据错乱的现象
如何避免:
将操作数据的部分加锁处理
会将并发变成串行牺牲了效率但是保证了数据的安全

抢锁
acquire()
释放锁
release()
抢锁步骤是由操作系统决定的,即到底谁将会被分配到锁,进而可以被执行.
只有当程序释放了锁,之后的程序才能继续去抢锁.




进程间通信IPC机制
一般进程间通信会使用队列这一数据类型来进行数据通信.
ps:

"""
队列:先进先出
堆栈:先进后出
"""
示例:
from multiprocessing import Queue


q = Queue(5) # 括号内可以传参数 表示的是这个队列的最大存储数
# 往队列中添加数据
q.put(1)
q.put(2)
# print(q.full()) # 判断队列是否满了
q.put(3)
q.put(4)
q.put(5)
# print(q.full())
# q.put(6) # 当队列满了之后 再放入数据 不会报错 会原地等待 直到队列中有数据被取走(阻塞态)

print(q.get())
print(q.get())
print(q.get())
print(q.empty()) # 判断队列中的数据是否取完
print(q.get())
print(q.get())
print(q.empty())
# print(q.get_nowait()) # 取值 没有值不等待直接报错
# print(q.get()) # 当队列中的数据被取完之后 再次获取 程序会阻塞 直到有人往队列中放入值
"""
full
get_nowait
empty
都不适用于多进程的情况
"""
from multiprocessing import Process,Queue

def producer(q):
q.put('hello GF~')

def consumer(q):
print(q.get())

if __name__ == '__main__':
q = Queue()
p = Process(target=producer,args=(q,))
c = Process(target=consumer, args=(q,))
p.start()
c.start()


"""
子进程放数据 主进程获取数据
两个子进程相互放 取数据
"""


生产者消费者模型
"""
生产者:生产/制造数据的
消费者:消费/处理数据的
这其中有时会出现下列情况
用大白话讲:做包子的,买包子的
1.做包子远比买包子的多
2.做包子的远比包子的少
供求不平衡的问题
"""
代码示例:

方法一:

使用了jionableQueue方法,

方法二:
#手动为每个进程设置等待:

from multiprocessing import Process,Queue,joinableQueue
import random , time
def producer(name, food ,q):
for i in range(4)
data = f'{name}生产了{food},第{i+1}号'
time.sleep(random.random()) # 生产间隔
q.put(data)
print(data)

def consumer (name,q):
while True:
data=q.get()
if data == None :
print('吃完了,也做完了')
break
print(f'{name}生产了{data})
time.sleep(random.random() #消化时间
q.task_done() # 告诉队列已经从队列中取出了一个数据,并且已经处理完毕

if __name__ == '__main__':
q = JoinableQueue() #创建对象

p = Process(target=producer,args=('大厨egon','馒头',q))
p1 = Process(target=producer,args=('墩子tank','生蚝',q))
# c = Process(target=consumer,args=('小小',q))
c1 = Process(target=consumer,args=('吃货',q))
p.start()
p1.start()
# c.daemon = True
c1.daemon = True
# c.start()
c1.start()
p.join()
p1.join()

q.join() # 等到队列中数据全部取出
# q.put(None)
# q.put(None)


结果如下:
墩子tank生产了生蚝.5号
吃货吃了墩子tank生产了生蚝.5号
大厨egon生产了馒头.4号
吃货吃了大厨egon生产了馒头.4号
墩子tank生产了生蚝.2号
大厨egon生产了馒头.3号
大厨egon生产了馒头.9号
吃货吃了墩子tank生产了生蚝.2号
大厨egon生产了馒头.1号
墩子tank生产了生蚝.7号
吃货吃了大厨egon生产了馒头.3号
吃货吃了大厨egon生产了馒头.9号
吃货吃了大厨egon生产了馒头.1号
墩子tank生产了生蚝.8号
吃货吃了墩子tank生产了生蚝.7号
吃货吃了墩子tank生产了生蚝.8号


Process finished with exit code 0


线程理论


什么是线程
进程线程其实都是虚拟单位,都是用来帮助我们形象的描述某种事物

进程:资源单位
线程:执行单位
将内存比如成工厂
那么进程就相当于是工厂里面的车间
而你的线程就相当于是车间里面的流水线
ps:每个进程都自带一个线程,线程才是真正的执行单位,进程只是在线程运行过程中
提供代码运行所需要的资源


为什么要有线程
开进程
1.申请内存空间 耗资源
2."拷贝代码" 耗资源

开线程
一个进程内可以起多个线程,并且线程与线程之间数据是共享的
ps:开启线程的开销要远远小于开启进程的开销

from threading import Thread
import time

def task(name):
print('%s is running'%name)
time.sleep(3)
print('%s is over'%name)
# 开线程不需要在__main__代码块内 但是习惯性的还是写在__main__代码块内
t = Thread(target=task,args=('egon',))
t.start() # 告诉操作系统开辟一个线程 线程的开销远远小于进程
# 小的代码执行完 线程就已经开启了
print('主线程')

from threading import Thread
import time

class MyThread(Thread):
def __init__(self,name):
super().__init__()
self.name = name

def run(self):
print('%s is running'%self.name)
time.sleep(3)
print('%s is over'%self.name)

t = MyThread('egon')
t.start()
print('主线程')

执行结果:
egon is running

egon is running

egon is over
egon is over


join方法

from threading import Thread,current_thread,active_count
import time
import os

def task(name,i):
print('%s is running'%name)
# print('子current_thread:',current_thread().name)
# print('子',os.getpid())
time.sleep(i)

print('%s is over'%name)
# 开线程不需要在__main__代码块内 但是习惯性的还是写在__main__代码块内
t = Thread(target=task,args=('egon',1))
t1 = Thread(target=task,args=('jason',2))
t.start() # 告诉操作系统开辟一个线程 线程的开销远远小于进程
t1.start() # 告诉操作系统开辟一个线程 线程的开销远远小于进程
t1.join() # 主线程等待子线程运行完毕
print('当前正在活跃的线程数',active_count())
# 小的代码执行完 线程就已经开启了
print('主')
# print('主current_thread:',current_thread().name)
# print('主',os.getpid())


线程间通信
注意 与进程不同的是 ,同一进程下的多线程是共用该进程下的资源的,

即他们之间使用的可以说是同一名称空间,

from threading import Thread
money = 666
def test():
global money
money = 999
t = Thread(target=task)
t.start()
t.join()
print(money)
执行结果:
999
线程对象及其他方法
from threading import Thread,current_thread
import time

守护线程:

def task(i):
print(current_thread().name)
time.sleep(i)
print('GG')
# for i in range(3):
# t = Thread(target=task,args=(i,))
# t.start()
t = Thread(target=task,args=(1,))
t.daemon = True
t.start()
print('主')
# 主线程运行结束之后需要等待子线程结束才能结束呢?
"""
主线程的结束也就意味着进程的结束
主线程必须等待其他非守护线程的结束才能结束
(意味子线程在运行的时候需要使用进程中的资源,而主线程一旦结束了资源也就销毁了)
"""


举例子:

from threading import Thread
from multiprocessing import Process
import time
def foo():
print(123)
time.sleep(1)
print("end123")

def bar():
print(456)
time.sleep(3)
print("end456")

def run():
print('789')
time.sleep(2)
print('end789')

if __name__ == '__main__':
t1=Thread(target=foo)
t2=Thread(target=bar)
t3= Thread(target=run)
t2.daemon=True
t1.start()
t2.start()
t3.start()
print("main-------开始")
执行结果:
123
456
789
main-------开始
end123
end789


可以看到 尽管我们使用了守护进程,即将主线程设置为t2的守护进程,当主线程死亡,则t2被强制杀死,那么结果就不包含'end456'.

线程互斥锁:
与操作进程互斥锁同理,当我们想要安全的增删改查数据,最后对操作数据的步骤加锁,以保证数据的安全性,操作和进程相同,即创建锁,抢锁,释放锁,

from threading import Thread,Lock
import time


n = 77

def task(mutex):
global n
mutex.acquire() # 抢锁
tmp = n
time.sleep(0.1)
n = tmp - 1
mutex.release() # 释放锁

t_list = []
mutex = Lock() # 示例化一个锁,即创建一个锁
for i in range(77):
t = Thread(target=task,args=(mutex,))
t.start()
t_list.append(t)
for t in t_list:
t.join()
print(n)

原文地址:https://www.cnblogs.com/Sunbreaker/p/11340703.html