目录
2. 循环创建子进程,可以先start,加入列表中,再执行join,下面这执行完一共3秒。
1 进程
1. windows 下进程要在main下创建
from multiprocessing import Process import time def task(name, n): print('%s开始运行' % name) time.sleep(n) print('%s结束运行' % name) if __name__ == '__main__': p1 = Process(target=task, args=('wang', 3)) # 用元组传参数,一个参数后也要加逗号 p2 = Process(target=task, args=('li', 4)) p1.start() # 激活进程 p2.start() p1.join() # 让主进程在进程执行完后再执行 p2.join() print("主进程")
2. 循环创建子进程,可以先start,加入列表中,再执行join,下面这执行完一共3秒。
start_time = time.time()
p_list = []
for i in range(1,4):
p = Process(target=task, args=('子进程%s' %i, i))
p.start()
p_list.append(p)
for p in p_list:
p.join()
print('主进程', time.time() - start_time)
3. 进程间数据相互隔离
n = 100 def test(): global n n = 600 print('子进程', n)if __name__ == '__main__':p = Process(target=test) p.start() p.join() print('主进程', n)
输出:子进程600
主进程100
在子进程中复制了一个n修改成了600,但不影响主进程的数据。
4. 计算机会给每一个进程分配一个PID号。
windows cmd进入命令行输入tasklist即可查看。
tasklist | findstr PID 查看具体进程
from multiprocessing import Process, current_process
import time
import os
def task2():
print('%s is running' % current_process().pid) # 查看当前进程PID(方法一)
print('%s is running' % os.getpid()) # 查看当前进程PID(方法二)
print('%s is running' % os.getppid()) # 查看当前进程的主进程PID(方法二)
time.sleep(30)
5.杀死进程
让操作系统去结束进程,需要一点时间,而代码的运行速度很快,所以要稍等一点时间。
p.terminate() # 杀死当前进程
time,sleep(0.1)
print(p.is_alive()) # 判断当前进程是否存活
6. 僵尸进程和孤儿进程
僵尸进程是杀死但没有死透, 没有立刻释放进程号, 之后会被父进程回收。
孤儿进程是子进程存活但父进程意外死亡,操作系统会管理和回收孤儿进程。
7.守护进程
主进程结束,守护进程也会结束。
这句话设置一定要放到p.start()上面,否则会报错。
p.daemon = True # 将p设置成守护进程
8. 互斥锁
多个进程操作同一份数据的时候(如十个人买一张票),会造成数据错乱。
解决办法就是加锁,将并发改成串行,牺牲效率保证数据安全。
只在数据互相争抢的时候加锁
9. 队列
from multiprocessing import Process, Queue
q = Queue(5) # 括号内参数表示最大可以存放数据
# 存数据 存满了继续存会阻塞
q.put(111)
print(q.full()) # 队列是否满
print(q.empty()) # 队列是否空
# 取数据 取完了再取会阻塞
v = q.get()
v1 = q.get_nowait() # 没有数据会报错
v3 = q.get(timeout=3) # 没有数据等三秒再报错
full, empty, get_nowait 在多进程时是不精确的。 队列 = 管道 + 锁
10.进程间通信 IPC
一个进程向队列中放数据,一个从队列中取数据。
def producer(q):
q.put('我是1号')
print('hello')
if __name__ == '__main__':
q = Queue()
p = Process(target=producer, args=(q,))
p.start()
print(q.get()) # 不用join ,get没拿到东西会继续等待
11.生产者消费者模型
两者间需要媒介沟通
要将消费者设置成主进程的守护进程p.deamon = True
不然消费者会一直get数据而阻塞
from multiprocessing import Process, Queue, JoinableQueue
if __name__ == '__main__':
q = JoinableQueue() # 每添加一个数据,计数器会加一
q.task_done() # 告诉队列已经取出来一个数据并处理完毕了
q.join() # 等队列所有数据被取完了再往后执行
12 Timer
延时一定时间后开始执行某函数
from threading import Timer
from threading import Thread
def func(x, y):
print(x + y)
if __name__ == '__main__':
t = Timer(3, func, args=(3,4))
t.start()
2 线程
进程是资源单位,线程是执行单位。 进程是工厂,线程是流水线。
每一个进程肯定自带一个线程。
进程开辟一块独立空间,线程执行时的资源招进程索要。
1.创建线程
和创建进程类似,但创建线程不需要复制代码,所以不用再main里创建,并且start后会快速开始
from threading import Thread
import time
def task(name):
print('%s is running' % name)
time.sleep(3)
print('%s is over' % name)
t = Thread(target=task, args=('www',))
t.start()
print('主')
2. TCP服务端并发效果
windows 不支持多进程,端口号会冲突,用多线程。
import socket
from threading import Thread
from multiprocessing import Process
'''
服务端要求:
1.固定的IP和PORT
2.24小时不间断服务
3.能够支持并发
'''
sever = socket.socket() # 默认是TCP协议
sever.bind(('127.0.0.1', 8080))
sever.listen(5)
def task(conn):
while True:
try:
data = conn.recv(1024)
if len(data) == 0: break
print(data.decode('utf-8'))
conn.send(data.upper())
except ConnectionError as e:
print(e)
break
while True:
conn, addr = sever.accept()
t = Thread(target=task, args=(conn,))
t.start()
3.线程补充方法
1 join()使主线程程等待线程运行完毕再继续
2 在线程中声明global 变量,线程间可以相互通信。
3 os.getpid() 主线程和子线程下PID号是一样的
from threading import Thread, active_count, current_thread
import time
def task(name):
print('hello', current_thread().name)
time.sleep(3)
if __name__ == '__main__':
t = Thread(target=task, args=('www',))
t.start()
print('主', active_count()) # 统计当前活跃的线程数
print('主', current_thread().name) # 获取线程名字
4.守护线程
主线程运行结束后不会立刻结束,要等其他非守护线程结束才会结束
在t.start()前加
t.daemon = True
注:如果非守护线程结束的慢而守护线程结束的快,那么全都会执行完毕。
5.互斥锁
from threading import Thread, Lock
import time
money = 100
def task():
global money
mutex.acquire() # 争抢锁
tmp = money
time.sleep(0.1)
money = tmp - 1
mutex.release() # 释放锁
if __name__ == '__main__':
mutex = Lock()
t_list = []
for i in range(100):
t = Thread(target=task)
t.start()
t_list.append(t)
for t in t_list:
t.join()
print(money)
6.GIL全局解释系统
“GIL不是python特点而是Cpython的特点
GIL是保证解释器级别的数据安全,是解释性语言的通病
GIL会导致同一个进程下的多个线程无法同时执行,即无法利用多核优势
针对不同的数据还是要加不同的锁”
在CPython解释器中GIL是一把互斥锁,用来阻止同一个进程的多线程的同时执行
因为CPython中的内存管理不是线程安全的
内存管理(垃圾回收机制)
1.应用计数
2.标记清除
3.分代回收
7.Python多线程是否有用(多核)
计算密集型
多进程优于多线程
IO密集型
多线程节省资源,优于多进程
多进程和多线程都有各自优势,实际使用中通常多进程下开设多线程。
8. 死锁(了解)
两把锁在对方手中,都需要枪对方的锁,导致程序阻塞。
这个代码会产生死锁,线程1执行func2时抢到了B锁,别的线程执行func1时抢到了A锁,双方都要对方的锁,导致程序卡住。
from threading import Thread, Lock, RLock
mutexA = Lock()
mutexB = Lock()
class MyThead(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
mutexA.acquire()
print('%s 抢到A锁' % self.name)
mutexB.acquire()
print('%s 抢到B锁' % self.name)
mutexB.release()
mutexA.release()
def func2(self):
mutexB.acquire()
print('%s 抢到B锁' % self.name)
time.sleep(2)
mutexA.acquire()
print('%s 抢到A锁' % self.name)
mutexA.release()
mutexB.release()
if __name__ == '__main__':
for i in range(10):
t = MyThead()
t.start()
9.递归锁(了解)
可以连续的acquire 和release
但只能被第一个抢到的执行操作内部有一个计数器,只有当计数器清零,才能被其他使用
把上个代码稍微改动,即可避免死锁。
from threading import Thread, Lock, RLock
mutexA = mutexB = RLock()
10.信号量(了解)
互斥锁相当于一堆人抢一个房间,信号量相当于一堆人抢好几个房间。
from threading import Thread, Lock, RLock, Semaphore
import random
import time
sm = Semaphore(5)
def task(name):
sm.acquire()
print('%s 正在通信' % name)
time.sleep(random.randint(1,5))
#time.sleep(3)
sm.release()
if __name__ == '__main__':
for i in range(20):
t = Thread(target=task, args=('第%d个人'%i,))
t.start()
11.Event事件(了解)
一些进程/线程要等待另外的一些进程/线程运行完毕后才能运行。
import time
from threading import Thread, Event
event = Event()
def light():
print('红灯亮着')
time.sleep(3)
print('绿灯亮了')
event.set()
def car(name):
print('%s 等红灯中' % name)
event.wait()
print('%s 车开走了' % name)
if __name__ == '__main__':
t = Thread(target=light)
t.start()
for i in range(20):
t = Thread(target=car, args=('%s' % i,))
t.start()
12.线程q(了解)
队列 = 管道 + 锁
先进先出q : 在进程中介绍过
后进先出q:q = queue.lifoqueue(3) put, get方法
优先级q: 可以给放入队列的数据设置优先级
q = queue.PriorityQueue(3)
q.put((10, '111')) 第一个参数是优先级,优先级可以是负数,且数字越小,优先级越高
13.进程池与线程池
因为硬件资源的限制,不能无限制的开设进程和线程。
要保证计算机能正常工作的情况下最大限度利用它。
池是用来保证计算机安全的情况下最大限度地利用下计算机,降低了程序的运行效率但是保证了计算机硬件的安全,从而让程序能正常工作。
线程池
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import time
pool = ThreadPoolExecutor(5) # 池子里固定5个,不会重复创建和销毁
# 不传参数默认是CPU核数的五倍线程数
# 只需要把任务提交到池子里即可
def task(n):
print(n)
time.sleep(1)
return n ** n
pool.submit(task, 1) # 提交任务 异步提交
print('主')
t_list = []
for i in range(20):
res = pool.submit(task, i)
# print(res.result()) # 调用了result方法,变成了串行,回调函数拿到了异步提交的结果 join
t_list.append(res)
pool.shutdown() # 关闭线程池, 等待线程池所有任务运行完毕
for t in t_list:
print('>>>', t.result()) # 结果时有序的
进程池
异步回调
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import time
pool = ProcessPoolExecutor() # 进程数固定,不会重复创建和销毁
# 不传默认是CPU的核数
def task(n):
print(n)
time.sleep(1)
return n ** n
def call_back(n):
print('call_back>>>', n.result())
# 回调机制 是绑定在进程身上的定时炸弹,一旦任务有结果立刻触发爆炸
if __name__ == '__main__':
t_list = []
for i in range(20):
res = pool.submit(task, i).add_done_callback(call_back)
总结
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
pool = ProcessPoolExecutor()
res = pool.submit(task, i).add_done_callback(call_back)
3 协程
协程是想象出来的,根本不存在。指的是单线程下,实现并发。
程序员自己在代码层面上检测所有IO操作,一旦遇到IO,在代码级别完成切换,从而使CPU觉得程序一直在运行,提高了程序运行效率。
多道技术:
切换+保存状态 yield
CPU切换两种状态:
1.程序遇到IO
2.程序长时间占用。
TCP服务端 accept recv
切换不一定提升效率(IO),也可能降低效率(计算)
1.gevent模块
检测IO操作,自动切换。耗时和最长时间有关。
from gevent import monkey
monkey.patch_all()
import time
from gevent import spawn
def heng():
print("hengheng")
time.sleep(2)
def ha():
print("haha")
time.sleep(3)
start_time = time.time()
g1 = spawn(heng)
g2 = spawn(ha)
g1.join()
g2.join()
print(time.time() - start_time)
单线程也能玩出并发的效果,可以在多进程开设多线程,多线程下开多协程,从而使程序执行效率提高。
4 IO模型
recv , accept, recvfrom使IO状态。
1.阻塞IO
开设了多进程多线程,依然没有解决IO问题。该等的地方还是在等,只不过多个人等待互不干扰。
2.非阻塞IO
提交之后无论是否有数据,都会立刻得到一个结果, 不会阻塞住。
将所有的阻塞操作都变成了非阻塞。
import socket
import time
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
server.setblocking(False) # 将所有的网络阻塞变成非阻塞
r_list = []
del_list = []
while True:
try:
conn, addr = server.accept()
r_list.append(conn)
except BlockingIOError:
# time.sleep(0.1)
# print('列表的长度', len(r_list))
# print('做其他事')
for conn in r_list:
try:
data = conn.recv(1024) # 没有消息会报错
if len(data) == 0: # 客户端断开链接
conn.close()
del_list.append(conn)
continue
conn.send(data.upper())
except BlockingIOError:
continue
except ConnectionError:
conn.close()
del_list.append(conn)
for conn in del_list: # 清除无用的链接
r_list.remove(conn)
del_list.clear()
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8080))
while True:
msg = input('>>>')
client.send(msg.encode('utf-8'))
data = client.recv(1024)
print(data)
虽然非阻塞IO很厉害,但是该模型会长时间占用着CPU空转,浪费在了while True循环上
实际应用中也不会开率使用非阻塞IO模型
3.IO多路复用
操作系统提供监管机制,监管socket 和conn对象,只要被触发了,立刻返回可执行对象。
监管一个对象时,效率低于阻塞IO,但可以监管多个对象。
selestors模块
4. 异步IO
是所有模型中效率最高,使用最广泛的模型。
模块 asyncio模块
框架 sanic tronado twisted
http://www.cnblogs.com/linhaifeng/articles/5937962.html
5.网络并发编程知识点
1.软件开发架构 CS BS
2.互联网协议 osi七层/五层协议 每层作用,以太网协议,广播风暴,IP协议
3.TCP UDP
4. 三次握手四次挥手(重点)
5.socket简介
6.TCP粘包问题,定制固定长度的报头
7.UDP协议
8.socketsever模块
1.操作系统发展
2.多道技术
3.进程理论
4.开启进程的两种方式
5.互斥锁
6生产者消费者模型
7.线程理论
8.开启线程的两种方式
9.GIL全局解释器
10.进程/线程使用
11.协程概念
12.IO模型了解