python高级编程之并发编程

多任务

介绍多任务就是计算机系统可以同时运行多个程序。好比你可以听着音乐的同时打开word赶作业,这就是多任务。

多任务-线程

运用线程技术是可以实现多任务的一种途径。
要想使用python创建线程则需要用到python的thread模块,该模块里面的Threading包对该操作进行了一个简单的封装

  1. 使用threading模块
    单线程:平时用我们使用的python创建的程序都属于单线程,一次执行一个
    多线程
    创建线程需要使用Threading模块的Thread(target = 函数名,args= 实参)函数,用于创建一个线程对象
    使用start()启动线程
    该函数一执行,该对象就会创建一个子线程,指向target函数的位置,此时该子线程是一个独立的线程和主线程并行执行。
    主线程执行完会等待所有子线程执行完后结束,

创建线程

import threading
import time

def test1():
   print("========test1-============");

def test2():
   print("========test1-============");


def main():
   # 创建线程对象
   t1 = threading.Thread(target=test1);
   t2 = threading.Thread(target=test2);
   t1.start();
   t2.start();

if __name__ == "__main__":
   main();

查看线程数量

threading.enumerate()
enumerate:返回回一个元组,将全部数据存储为一个元组
再使用Len()方法就可以返回当前的线程数

互斥锁

问题:如果当两个进程共享同一个全局变量时,又因为进程的运行不确定,可能会出现这一个进程带着这个全局变量运行到一半,cpu可能就会将该进程给撵出去,不让它继续执行,而让另一个进程执行,这就会导致最后这个全局的值不正确,使用互斥锁就很好的解决了这个问题。
*互斥锁顾名思义就是一把锁,用于将该运行的就成锁起来,在锁起来的时候其他进程就无法进来,只有当该锁解开后其他进程才会进来执行
创建:

# 创建互斥锁
# 互斥锁对象变量  = threading.Lock();
t1 = threading.Lock();

上锁

# 上锁 对象名 .acquire()
# 解锁 对象名.release()

t1.acquire();
代码。。。。
t1.release();

死锁

在进程共享多个资源时,有两个进程a,b,如果a进程等待b进程资源,b进程在等待a进程资源,这时就形成了一个死锁,谁都不让谁。

避免死锁

  • 程序设计时避免死锁(使用银行家算法)
  • 添加超时时间

多任务进程

程序:有代码编写、编译生成的一个exe文件就是一个程序,静态的。
进程:一个程序运行起来所用到的资源就称为进程。

进程实现多任务

Python中的multiprocessing模块可以实现,该模块是一个跨平台的多进程模块,该模块中process包提供了一个进程对象
创建进程

import multiprocessing
import time

def test1():
   print("========test1-============");

def test2():
   print("========test1-============");


def main():
   # 创建线程对象
   t1 = multiprocessing.process(target=test1);
   t2 = multiprocessing.process(target=test2);
   t1.start();
   t2.start();

if __name__ == "__main__":
   main();

说明
创建进程时只需要传入一个执行函数的参数,创建一个process实例,使用strat()方法启动

进程和线程的区别

进程实现多任务:进程是将该程序另外复制了一份单独运行,之间没有共同的资源,相互之间是独立的。好比手机或计算机的程序双开,应用成语分身,是一个相互独立的之间没有任何联系。

线程实现多任务:线程是在同一个程序中进行的,在同一个程序中每个线程执行一部分代码,同时执行,好比qq中的好友聊天,可以同时接受和发送数据。

注意
线程是存活在进程中的,一个进程中可以有多个线程同时运行,在工厂流水线中,进程就好比一条流水线,而线程就是在流水线上有固定任务的工人。
如果想再独立的实现这个功能,这时候就创建一个进程,该进程和上一个进程没有关系,怎么做都不影响上一个进程,是一个独立的。

进程之间的通信

在上问中我们也提到过,进程之间是相互独立的,但进程之间也可以进行通信。

  • 利用双通道技术的套接字
  • 利用文件实现
  • 利用队列

利用文件实现
不推荐,因为进程是在内存中存储的而文件是在硬盘中的,由于硬盘相对于内存运行速度太慢了
利用队列(Queue)
队列:是在内存中的一块空间,拥有先进先出的特点
创建队列
使用multiprocessing包中Queue(参数)方法创建一个队列对象
该参数是一个int型表示队列可以存储多少个数据,不写表示看你的内存空间
put(数据):入队列
get() : 出队列
full() :判断队列是否满
empty() : 判断队列是否为空

进程池Pool

进程池:将多个固定的进程存储到一起称为进程池。
当如果创建的子进程数量不多,可以使用process方法单独创建,但如果有成千上万个个目标,手动创建的工作量太大。这就需要使用进程池
初始化进程池时,可以指定一个最大的进程数,当有新的任务请求时,判断进程池如果没满就建一个新进程进去,但如果进程池中的进程达到了最大进程数,那么请求会等待,等到池中有有进程才会执行之前的任务。


创建进程池

使用multiprocessing中的Pool包

# 创建进程池

# 导入模块包
from multiprocessing import Pool
# 进程池对象 = multiprocessing.Pool(5);	定义一个进程池,最大进程数5
po = Pool(5);

# 向进程池添加任务
# 使用apply_async(函数名,参数元组);
po.apply_async()

#关闭进程池
# close()方法,该方法一调用,进程池会关闭,关闭后po不在接受请求
po.close();

# 如果子进程没有结束,主进程不能结束
# join()方法解堵塞,等待子进程结束,主进程才继续执行,该方法一定要在close()后面

迭代对象

判断是否为可迭代的对象

如果可以使用for in 进行迭代的对象就是可迭代对象。
比如列表、元组、字典以及字符串都是可以迭代的。
判断该对象是否为可以迭代的可以使用collections模块
该模块中有一个Iterable包,
使用该包中的isinstance(对象,要判断的迭代类型)方法
迭代对象:Iterable
如果有个对象为classmate
判断该对象是否是可迭代的:
isinstance(classmate,Iterable);
返回类型为bool型

迭代对象的原理

迭代对象都有一个__iter__方法,也就是说要想成为可迭代的对象必须创建__iter__方法,该方法返回一个迭代器对象

迭代器

要想从迭代对象中取得数据,必须要有一个迭代器,该迭代器对象中有一个__next__方法,没调用一次迭代对象,迭代器就从__next__方法中返回一个值,也就是说要想成为迭代器对象必须要有一个__next__方法。
判断该对象是否是一个迭代器对象也可以使用ininstance()方法
首先在collections模块中导入Iterator包
迭代器对象:Iterator
isinstance(classmate,Iterator);
返回类型为bool型
优点占用极少的空间来实现.

for in机制

1、首先判断该对象是否是可迭代的
2、调用可迭代对象中__iter__方法,该方法返回一个迭代器对象
3、调用迭代器对象中的__next__方法,for in 就是小循环一次在__next__中取出一个数据

创建可迭代对象

# -*- codeing = utf-8 -*-
# @Time : 2020/11/12 10:25
# @Author : King
# @File : 创建迭代对象.py
# @software : PyCharm

from collections import Iterable
from collections import Iterator

import time
# 创建可迭代对象
class classMate(object):
    def __init__(self):
        self.names = list();
    def add(self,name):
        self.names.append(name);
    def __iter__(self):
        return classIterator(self);     #返回一个迭代器对象,将整个对象作为参数传过去


class classIterator(object):

    def __init__(self,obj):
        self.obj=obj;       #接受该对象
        self.cut = 0;

    # 从该方法中取值
    def __next__(self):
        if(self.cut<len(self.obj.names)):
            #print(len(self.obj.names));
            re = self.obj.names[self.cut];
            self.cut += 1;
            return re;
        else:
            raise StopIteration;        #否则抛出停止异常
def main():
    classmate = classMate();
    classmate.add("张三");
    classmate.add("李四");
    classmate.add("王五");

    for name in classmate:
        print(name);
        time.sleep(1);

   # print(isinstance(classmate,Iterable));     #判断该对象是否为可迭代的对象



if __name__ == "__main__":
    main();

注意如果当数据已经取完了,必须要将该for in 停止;这就需要使用raise StopIteration异常,使用raise抛出停止异常将for in停止。

完善迭代器

因为当一个方法拥有 iter 方法就是一个可迭代对象,拥有一个 next 方法就是一个迭代器对象,
所有我们可以将这两个方法在一个对象中实现,让 iter 方法的返回值指向自己,这样我们也可以实现,这样我们就可以节省一个类的资源。

# -*- codeing = utf-8 -*-
# @Time : 2020/11/12 10:25
# @Author : King
# @File : 创建迭代对象.py
# @software : PyCharm

from collections import Iterable
from collections import Iterator

import time
# 创建可迭代对象
class classMate(object):
    def __init__(self):
        self.names = list();
        self.cut = 0;
    def add(self,name):
        self.names.append(name);
    def __iter__(self):
        return self;     #指向自己,在自己对象中查找__next__方法
   
   	# 实现迭代器
   	def __next__(self)
   		if(self.cut<len(self.names)):
            #print(len(self.names));
            re = self.names[self.cut];
            self.cut += 1;
            return re;
        else:
            raise StopIteration;        #否则抛出停止异常

def main():
    classmate = classMate();
    classmate.add("张三");
    classmate.add("李四");
    classmate.add("王五");

    for name in classmate:
        print(name);
        time.sleep(1);

   # print(isinstance(classmate,Iterable));     #判断该对象是否为可迭代的对象



if __name__ == "__main__":
    main();

生成器

当一个函数中有一个yield语句,那么这个函数就不再是一个函数了而是一个生成器模板,而且调用该函数的语句也不再是调用函数而是创建一个生成器对象。

原理

当执行到创建生成器对象时,会进去该模板执行语句,当执行到yield语句时,会将yield语句后面跟着的只返回到调用该对象的地方,例如可以使用for in 进行取值,也可以使用next()函数进行取值。再次执行该对象时,不是从开始执行而是从上次结束的地方执行。

使用生成器创建斐波那契数列


def create_feboncc(all_num):
a, b = 0, 1;
cur = 0;
while cur < all_num:
	yield a;		#将a返回到调用对象的地方并等待对象再次执行
	a, b = b, a+b;
	cur += 1;

obj = create_feboncc(10)	# 此处不再是调用函数而是创建生成器对象,并yield返回的值使用obj接受
for num = obj:
	print(num);		#使用num将该值接受并输出

使用next()函数接受数据

next(生成器对象);
next()用于接受由yield语句返回的值

使用send(参数)方法接受数据

使用send()可以达到next()方法同样的效果
不同的是send()可以性向里面传参数,该参数会在生成器模块中接受
一般是在yield语句使用一个变量接受:例:ans = yield a
使用ans接受send传递的值,该值一把用来重新定义生成器模块

注意 send一般不会放到第一次启动生成器,如果非要这么做那么床底none值

使用yield实现多任务


def test_1():
	while True:
		print("-----1----");
		time.sleep(0.1);
		yield				# 暂停程序并返回

def test_2():
	while True:
		print("-----2----");
		time.sleep(0.1);
		yield

t1 = test_1();
t2 = test_2();
while True:
	next(t1);				
	next(t2);

使用greenlet、geven完成多任务

greenlet是对yield切换进行封装的一个模块。从而对多任务的切换更加简单
使用前必须先安装该模块
linux方法:sudo pip3 install greenlet

from greenlet import greenlet
import time

def test_1():
    while True:
        print("---1---");
        gre2.switch();      #切换到test_2中执行
        #time.sleep(0.5);

def test_2():
    while True:
        print("---2---");
        gre1.switch();      #切换到test_1中执行
        #time.sleep(0.5);


#创建对象
gre1 = greenlet(test_1);
gre2 = greenlet(test_2);

gre1.switch();      #切换到test_1中执行

使用gevent实现多任务

使用greenlet我们已经实现了多任务,但还的人工切换,是不是很麻烦,这就需要使用gevent不需要人工切换,可以实现自动切换。
其原理,当我们在执行是遇到了一个延时那么他会自动的切换到其他的greenlet,再在适当的时候切换回来。

使用前必须先安装该模块
linux方法:sudo pip3 install gevent

import gevent

def test(a):
    print("----%d---" % a);
    gevent.sleep(0.5);            # 添加延迟以便切换下一个,实现多任务

#创建一个greenlet对象,spawn(function,ares);   第一个参数是要实现的函数,第二个参数是该函数的参数
ge1 = gevent.spawn(test,1);        
ge2 = gevent.spawn(test,2);         
ge3 = gevent.spawn(test,3);

# 开启greenlet
ge1.join()
ge2.join()
ge2.join()

注意该程序中的所有延迟语句必须使用gevent中的延迟
monkey.patch_all(),如果该程序中有很多本来的延迟一个一个的换太麻烦,可以在程序开头使用**monkey.patch_all()**将程序中的所有延迟替换为gevent的延迟,使用时必须导入monley包
gevent.joinall([]):如果我们的greenlet语句太多,我们还需要写那么多个join语句,这样一个一个写太麻烦,我们可以使用gevent.joinall语句,该语句时等待所有的greentlet语句执行完结束

import gevent

def test(a):
    print("----%d---" % a);
    gevent.sleep(0.5);            # 添加延迟以便切换下一个,实现多任务

#创建一个greenlet对象,spawn(function,ares);   第一个参数是要实现的函数,第二个参数是该函数的参数
gevent.joinall([
ge1 = gevent.spawn(test,1),      
ge2 = gevent.spawn(test,2),         
ge3 = gevent.spawn(test,3)])

总结

  • 进程是操作系统资源分配的单元
  • 线程是操作系统调度的单元
  • 进程切换需要的资源很大,进程稳定,各进程之间没有影响
  • 协程(greenlet)切换任务资源小,效率高。不稳定
  • 并行:一个核心运行一个任务多个任务同时运行,进程和线程根据cup核心可能是并行
  • 并发:多个任务一个核心切换运行,协程是在一个线程中运行所以是并发
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值