第九篇:并发编程

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()           		#等待t1走完再往下走
t2.setDaemon(True)          #把t2设为守护线程,t2跟着主线程走,主线程结束它结束
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
    #由于cpu的切换,导致num最终取值不为0
    # tem = num
    # time.sleep(0.01)
    # num = tem -1
	
    #为了保证数据安全,即不让cpu切换,通过在不让切换的位置加锁
    #三步:定义锁、加锁、释放
    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
进程包括主进程和子进程,进程的调用方式和线程一样,分为两种,主动定义和继承
调用方式1from 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......')
调用方式2import 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)
    # print('son process',id(q))
    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)
    # print(i)
    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:全程无阻塞

###################blocking 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')
###################nonblocking IO
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
###################IO 多路复用
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'))
###################selectors模块的应用
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()       #[sock,conn]会自动识别哪个发生变化
    for key,mask in events:
        callback = key.data
        print(callback)         #accept()或read()
        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'))
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值