1、进程和线程
进程:一个程序在一个数据集上的一次动态执行过程。
进程一般由程序、数据集和进程控制块三部分组成。
数据集:程序在执行过程中所需要的资源。
进程控制块:用来记录进程的外部特征,描述进程的执行变换,系统用来管理和控制进程,
是系统感知进程的唯一标志。
线程
线程的出现是为了降低上下文的消耗,提供系统的并发性,并突破一个进程只能干一样事的缺陷,
使得进程内并发成为可能。
线程:轻量级进程,它是一个基本的cpu执行单元,也是程序执行过程中的最小单元,
由线程ID、程序计数器、寄存集合器和堆栈共同组成。线程的引入减小了程序并发执行的开销,
提高了系统的并发性能。线程没有自己的系统资源。
区别:
一个程序至少有一个进程,一个进程至少有一个线程(主线程);
进程在执行过程中,拥有独立的内存单元,而多个线程内存共享;
每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口,
但是线程不能独立运行,必须依托于应用程序中,由应用程序提供线程控制;
进程是进行资源分配和调度的一个独立单元。
创建线程(方法一)
import threading
def musci():
print('begin1 %s'%time.ctime())
time.sleep(3)
print('stop1 %s'%time.ctime())
def play():
print('begin1 %s'%time.ctime())
time.sleep(3)
print('stop1 %s'%time.ctime())
t1 = threading.Thread(target=musci)
t2 = threading.Thread(target=play)
t1.start()
t1.join()
t2.setDaemon(True)
t2.start()
print('>>>>>>>')
t.setDaemon(True):将线程设为守护线程,必须在t.start()之前,这个方法和jion用法相反,
当主线程和子线程分别运行时,当主线程完成想要退出,会检验子线程是否完成,如果完成则退出,
如果没有,则等待子线程完成后再退出。而setDemon会帮助主线程完后之后不用等待即退出。
创建线程(方法二)
class Mythread(threading.Thread):
def __init__(self,num):
super().__init__()
self.num = num
def run(self):
print('>>>>>>>>')
time.sleep(3)
t1 = Mythread(1)
t1.start()
3、python的GIL(全局解释锁)
无论启动多少个线程,无论有多少个CPU,python在执行的时候每个进程会
在同一时刻只允许一个线程运行。
4、并发/并行
并发:系统具有处理多个任务(动作)的能力
并行:系统具有同时处理多个任务(动作)的能力
5、同步/异步
同步:当进程执行到一个IO(等待外部数据)的时候就等待
异步:当进程执行到一个IO(等待外部数据)的时候不等待,等数据接收成功再回来处理。
任务:IO密集型(sleep属于IO)
计算密集型
IO阻塞和时间轮循会导致cpu在线程间进醒切换。
对于IO密集型的任务,python是有意义的,可以采用多进程+协程的方式
对于计算密集型的任务,python多线程不推荐。
6、同步锁(重点)
import threading,time
def sub():
global num
lock.acquire()
tem = num
time.sleep(0.01)
num = tem -1
lock.release()
num = 100
l = []
lock = threading.Lock()
for i in range(100):
t = threading.Thread(target=sub)
t.start()
l.append(t)
for i in l:
i.join()
print(num)
7、死锁和递归锁(重点)
当线程共享多个资源的时候,如果两个线程分别占有一部分资源并同时等待对方的资源,就会造成死锁。
解决办法不用一般的锁,采用递归锁,递归锁的元类是内部存在计数器,当每设置一次锁,计数器加一1
当计数器的值大于1,其他线程就不能进入,一次只能有一个线程。
递归锁的用法和普通锁一样,均是三步。
r_lock = threading.RLock()
8、同步对象(了解)
import threading
event = threading.Event()
event.wait() 等待,只有event.set()被设定才继续走
event.set()
event.claer() 一旦设定被解除,遇到event.wait()又继续等待
event对象在多个线程中的状态是一致的
9、信号量(了解)
信号量:用来控制线程并发数的,即同时可以开几个。
import threading
semaphore = threading.Semaphore(5)
semaphore.acquire()
semaphore.release()
实质一把锁,设计了5把钥匙,一次可以进5个线程。
10、线程队列(重点)
队列和多线程配合使用
import queue
q = queue.Queue(5) 先进先出模式,队列中最多放5个值
q.put(12)
q.put('hello') 队列中放数据
q.put_nowait(56) 等于q.put(56,block=False),当队列放不下会报错
q.get() 从队列中按顺序取数据
q.get_nowait() 等于q.get(block=False),当队列为空时会报错
q.qsize() 队列中有多少数据
q.empty() 判断队列是否为空
q.full() 判断队列是否满
q.task_done() 当队列完成一个取值或放值得动作后,发送提示信息已完成了
q.join() 接收task_done信号,如果没有收到会等待。
q = queue.LifoQueue(5) 先进后出模式
q = queue.PriorityQueue(5) 自己设定优先级
q.put([2,'hello'])
q.put([3,57]) 放入列表,第一个为优先级别(数字越小,先出),第二个为内容
11、多进程
多进程是为了解决GIL
进程包括主进程和子进程,进程的调用方式和线程一样,分为两种,主动定义和继承
调用方式1:
from multiprocessing import Process
import time
def f(name):
print('parent process',os.getppid())
print('process id',os.getpid())
time.sleep(1)
print('hello',name,time.ctime())
if __name__ == '__main__':
l = []
for i in range(3):
p = Process(target=f,args=('yuan',))
l.append(p)
p.start()
for i in l:
i.join()
print('end......')
调用方式2:
import time
from multiprocessing import Process
class MyProcess(Process):
def run(self):
time.sleep(1)
print('hello',self.name,time.ctime())
if __name__ == '__main__':
l = []
for i in range(3):
p = MyProcess()
p.demon = True
l.append(p)
p.start()
for i in l:
i.join()
print('end.......')
12、进程间通信(三种方式)
进程队列Queue和Pipe只是实现了数据交换,并没有实现数据共享(一个进程去更改另一个进程数据)
Manager实现数据共享,进程间通信应该尽量避免使用共享数据的方式
进程队列Queue:
from multiprocessing import Process,Queue
import time
def foo(q):
time.sleep(1)
q.put(123)
q.put('yuan')
if __name__ == '__main__':
q = Queue()
p = Process(target=foo,args=(q,))
p.start()
p.join()
print(q.get())
print(q.get())
Pipe:用法类似socket中的conn,在主进程中创建,需要先传给子进程
from multiprocessing import Process,Pipe
def f(conn):
conn.send([12,{'name':'alex'},56])
res = conn.recv()
print(res)
print(id(conn))
if __name__ == '__main__':
par_conn,chi_conn = Pipe()
print(id(par_conn))
p = Process(target=f,args=(chi_conn,))
p.start()
print(par_conn.recv())
par_conn.send('你好')
p.join()
Manager:
from multiprocessing import Process,Manager
def f(d,l,n):
d[n]='1'
d['2']=2
l.append(n)
print('son process',id(d),id(l))
if __name__ == '__main__':
with Manager() as manage:
d = manage.dict()
l = manage.list(range(5))
p_l = []
for i in range(3):
p = Process(target=f,args=(d,l,i,))
p_l.append(p)
p.start()
for i in p_l:
i.join()
print(d)
print(l)
13、进程锁和进程池
进程锁 虽然进程之间的数据独立,但是一些资源不是独立的。进程锁的用法和线程锁一样,
但需先传给子进程。
from multiprocessing import Process,Lock
import time
def f(lock,num):
lock.acquire()
time.sleep(1)
print('hello world')
lock.release()
if __name__ == '__main__':
lock = Lock()
for num in range(10):
p = Process(target=f,args=(lock,num))
p.start()
p.join()
print('ending ...')
线程池
from multiprocessing import Process,Pool
import time,os
def foo(i):
time.sleep(1)
return i
def bar(arg):
print(arg)
if __name__ == '__main__':
pool = Pool(5)
for i in range(50):
pool.apply_async(func=foo,args=(i,),callback=bar)
pool.close()
pool.join()
14、协程(关键字参数yield)
python多线程没有办法利用多核,只能通过不断的切换,不能真正实现并行的效果
但对于IO密集型的任务是有意义的,如果想利用多核可以利用多线程。
协程: ----》非抢占式
协程和进程 ----》抢占式的程序
协程本质上是一个线程,协程的优势是没有切换消耗,没有锁的概念,但是不能利用多核,
可采用多进程+协程的方式。协程的关键:什么时候切换
import time
def consumer(name):
print('准备吃包子了....')
while True:
new_baozi = yield
print('%s is eating baozi %s'%(name,new_baozi))
def producer():
con1.__next__()
con2.__next__()
n = 0
while n<20:
time.sleep(1)
print('producer is making baozi %s and %s'%(n,n+1))
con1.send(n)
con2.send(n+1)
n +=2
if __name__ == '__main__':
con1 = consumer('c1')
con2 = consumer('c2')
producer()
greenlet 收到切换
gr1 = greenlet(f)
gr1.switch()
利用此方法实现手动切换,每次切换需借助g.switch()
gevent 实现自动切换(重点)
import gevent,requests,time
start = time.time()
def f(url):
print('GET:%s'%url)
resp = requests.get(url)
data = resp.text
gevent.spawn(f,'https://www.baidu.com/') 一个
gevent.joinall([gevent.spawn(f,'https://www.baidu.com/'), 多个
gevent.spawn(f,'https://www.baidu.com/')])
print('cost time',time.time()-start)
15、事件驱动
事件驱动是一个编程范式,程序的执行由外部事物决定,特定是包含一个事件循环,
当外部事件发生时使用回调机制来触发相应处理。另外两只编程范式:(单线程)同步和多线程编程。
事件驱动的监听事件是由操作系统调用cpu来完成。
16、常见的IO模型
阻塞IO、非阻塞IO、IO多路复用、异步IO、信号驱动IO(不常用)
阻塞IO:只发出一次系统调用,全程阻塞;
非阻塞IO:发出多次系统调用,但会造成数据处理不及时;
IO多路复用:利用select实现,可以同时监听多个链接,实现多路并发(poll\epoll\select);
触发方式:水平触发,只有在高电平和低电平触发
边缘触发:只有在电平发生变化时触发(突然有高到底或由底到高)
异步IO:全程无阻塞
import socket
sk = socket.socket()
sk.bind(('192.168.1.5',8080))
sk.listen(5)
while True:
conn,addr = sk.accept()
while True:
conn.send('hello client'.encode('utf8'))
data = conn.recv(1024)
print(data.decode('utf8'))
import socket
sk = socket.socket()
sk.connect_ex(('192.168.1.5',8080))
while True:
data = sk.recv(1024)
print(data.decode('utf8'))
sk.send(b'hello server')
import socket,time
sk = socket.socket()
sk.bind(('192.168.1.5',8080))
sk.listen(5)
sk.setblocking(False)
print('waiting client connection ...')
while True:
try:
conn,addr = sk.accept()
print(addr[0])
mes = conn.recv(1024)
print(mes.decode('utf8'))
conn.close()
except Exception as e:
print(e)
time.sleep(4)
import socket,time
sk = socket.socket()
sk.connect_ex(('192.168.1.5',8080))
while True:
sk.send(bytes('hello','utf8'))
time.sleep(2)
break
import socket,select
sk = socket.socket()
sk.bind(('192.168.1.5',8080))
sk.listen(5)
inp = [sk,]
while True:
r,w,e = select.select(inp,[],[],5)
for obj in r:
if obj == sk:
conn,addr = obj.accept()
print(conn)
inp.append(conn)
else:
data =obj.recv(1024)
print(data.decode('utf8'))
mes = input('回答%s号客户:'%inp.index(obj))
obj.send(mes.encode('utf8'))
print('>>>>>>>>')
import socket
sk = socket.socket()
sk.connect_ex(('192.168.1.5',8080))
while True:
inp = input('>>').strip()
sk.send(inp.encode('utf8'))
data = sk.recv(1024)
print(data.decode('utf8'))
import socket,selectors
def accept(sock,mask):
conn,addr = sock.accept()
print(conn,addr)
conn.setblocking(False)
sel.register(conn,selectors.EVENT_READ,read)
def read(conn,mask):
try:
data = conn.recv(1024)
if not data:
raise Exception
print(conn)
conn.send(data)
except Exception as e:
print('closing',conn)
sel.unregister(conn)
conn.close()
sock = socket.socket()
sock.bind(('192.168.1.5',8080))
sock.listen(5)
sel = selectors.DefaultSelector()
sock.setblocking(False)
sel.register(sock,selectors.EVENT_READ,accept)
print('server......')
while True:
events = sel.select()
for key,mask in events:
callback = key.data
print(callback)
callback(key.fileobj,mask)
import socket
sk =socket.socket()
sk.connect_ex(('192.168.1.5',8080))
print(sk)
while True:
msg = input('>>>')
sk.send(msg.encode('utf8'))
data = sk.recv(1024)
print(data.decode('utf8'))