多任务编程:进程、线程、协程、生成器、迭代器

  1. 多任务的线程
import time
import threading

def sing():
    for i in range(5):
        print("正在唱:钟无艳")
        time.sleep(1)
        
def dance():
    for i in range(5):
        print("正在跳popping")
        time.sleep(1)

def main():
    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)
    t1.start()
    t2.start()
    
if __name__ == "__main__":
    main()

以上 就达到了一个目的,唱歌和跳舞同时执行
这就是多任务,现在多核CPU已经非常普及了,但是即使过去的单核cpu也可以多任务,那么单核Cpu是怎么执行的呢, 就是A执行0.01切换到B又执行0.01感觉上是在同时执行,实际上只是单核切换速度快,视觉上达到了这个效果, 真正的并行执行多任务只能在多核CPU上实现,
以下总结以下:
并行: 真的多任务
并发: 假的多任务

线程:
python的thread模块是比较底层的模块,python的threading模块是对thread做了一些包装的,可以更加方便的被使用

  1. 使用threading模块
    单线程执行
import time

def saySorry():
    print("亲爱的,爷错了")
    time.sleep(1)
    
if __name__ == "__main__":
    for i in range(5):
        saySorry()

多线程执行

import threading
import time

def saySorry():
    print("亲爱的,爷错了")
    time.sleep(1)
    
if __name__ == "__main__":
    for i in range(5):
        t = threading.Thread(target=saySorry)
        t.start()

** 查看线程熟练 **

import time
import threading

def sing():
    for i in range(5):
        print("正在唱:钟无艳")
        time.sleep(1)
        
def dance():
    for i in range(10):
        print("正在跳popping")
        time.sleep(1)

def main():
    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)
    t1.start()
    t2.start()
    
    while True:
        # enumerate枚举 返回元组比如l =[1,2,3,4]
        # for i in enumerate(l)
        length = len(threading.enumerate())
        print(threading.enumerate())
        if length <=1:
            break
        time.sleep(1)
        
    
if __name__ == "__main__":
    main()

这里需要引入一个概论,就是主线程不能先于子线程结束。所以默认创建线程的时候,会让主线程等待子线程结束。注意 一般都是在start()的时候,子线程才会被创建。内部的函数执行完 线程才会结束。 并且多个线程之间默认没有先后执行的顺序。

线程执行代码的封装

通过使用threading模块能完成多任务的程序开发,为了让每个线程的封装性更好,使用threading模块时,一般会定义一个新的子类,然后重写run方法。

import threading
import time

class Mythread(threading.Thread):
    def run(self):
        for i in range(3):
            time.sleep(1)
            msg = self.name
            print(msg)
            
if __name__ == "__main__":
    t = MyThread()
    t.start()

说一下 start()方法 会自动调用run方法,run方法写上文提到的Thread(target = 函数名)里面的函数里面的东西,适合于线程的里面调用的方法比较复杂,需要分成多个函数的情况下,比如

import threading
import time

class Mythread(threading.Thread):
    def run(self):
        self.login()
        self.register()
        
    def login(self):
        print("登录")

    def register(self):
        print(注册)
        
if __name__ == "__main__":
    t = MyThread()
    t.start()

也就是说你创建了一个线程,这个子线程会顺序执行login和register 注意以上情况只能执行run方法里面调用的方法。别傻了吧唧的在t.start()后面去调用什么t.login()

多线程共享全局变量

num = 100
nums = [11,22]

def test():
    global num
    num+=100
def test2():
    nums.append(33)
print(num)
print(nums)
test()
test2()
print(num)
print(nums)

在一个函数中对全局变量进行修改的时候,到底是否需要使用goba进行说明
要看是否对全局变量的执行指向进行了修改
如果修改了执行,即让全局变量指向了一个新的地方,那么必须使用 global
如果,仅仅是修改了指向的空间中的数据,此时不用必须使用global
开始看看使用

import threading
import time
#定义一个全局变量
g_num = 100
def test1():
    global g.nun
    g_num += 1
    print("…… in test1 g_num=%d---"%gnun)
    
def test2():
    print("----in telt2 g_num=%d=---"%g_num)
    
def main():
    t1= threading Thread(target=test1)
    t2= threading Thread(target=test2)
    t1. start()
    time. sleep(1)
    t2. start()
    time. sleep(1)
    print("in main Thread g_nuns %s--"%g_num)
    
if __name__ = "__main__":
    main()

这里就是为了说明一个问题,多线程多任务程序,子进程和子进程之间共享全局变量,下面对例子做一个改变

import threading
import time
#定义一个全局变量
def test1(temp):
    temp.append(22)
    print("…… in test1 temp=%s---"%str(temp))
    
def test2(temp):
    print("----in telt2 temp=%d=---"%str(temp))
    
g_nums = [11,22]    

def main():
    # 这个args,是test1的形参
    t1= threading Thread(target=test1,args = (g_nums,))
    t2= threading Thread(target=test2,args = (g_nums,))
    t1. start()
    time. sleep(1)
    t2. start()
    time. sleep(1)
    print("in main Thread g_nums = %s--"%str(g_num))
    
if __name__ = "__main__":
    main()

共享全局变量问题

假设两个线程t1和以2都要对全局变量g_num(默认是0)进行加1运算·t1和t2都各对g_num加10次·g_num的
最终的结果应该为20·
但是由于是多线程同时操作·有可能出现下面情况

  1. 在g_num=0时·t1得g_num=0·此时系统把调度为”sleep状态·把t2转换为running状态t2
    也获得g_num=0
  2. 然后t2对得到的值进行加1并赋给g_num使得g_num=1
  3. 然后系统又把t2调度为”sleeping"·把t1转为running·线程t1又把它之前得到的0加1后赋值给
    g_num.
  4. 这样导致虽然:t1和t2都对g_num加1·但结果仍然是g_num=1
    也就是说线程之间发生了资源竞争。
# 定义一个全局变量
g_num = 0
def test1(num):
    lobal g_num
    for i in rage(num):
        g_num += 1
    print("---in test1 g_num=%d---"%g_num)
    
def test2(num):
    lobal g_num
    for i in rage(num):
        g_num += 1
    print("---in test2 g_num=%d---"%g_num)
def main():
    t1 threading Thread(target=test1, args=(100000,))
    t2 threading Thread(target=test2, args=(100000,))
    t1. start()
    t2. start()
    #等待上面的?个线程执行完
    time. sleep(5)
    print("---in main g_num=%d---"%g_num)

if __name__ == "__main__":
    main()

以上的情况,我们预想的是g_num这个值会加上100000两次,但是实际执行结果并不是这样,也就是说两个线程之间,他们发生了资源争夺,具体争夺过程看上文。

这么去解决这个问题呢,先引入一个同步的概念

同步就是协同步调·按预定的先后次序进行运行·比如你说完·我再说·
同字从字面上容易理解为一起动作
其实不是·“同字应是指协同·协助·互相配合
如进程·线程同步·可理解为进程或线程A和B一块配合·A执行到一定程度时要依靠B的某个结果·于是
停下来·示息B运行,B执行·再行结果给A,A再继续操作
解决线程同时修改全局变量的方式
上一小节提出的那个计算错误的问题·可以通过线程同步来进行解决
思路·如下

  1. 系统调用t1·然后获取到g_num的值为0·此时上一把锁,即不允许其他线程操作g_num
  2. t1对g_num的值进行+1
  3. t1解锁·此时g_num的值为1·其他的线程就可以使用g_num了·而且g_num的值不是0而是1
    4.同理其他线程在对g_num进行修改时·都要先上锁·处理完后再解锁·在上锁的整个过程中不允许其
    他线程访问·就保证了数据的正确

互斥锁
当多个线程几乎同时修改某一个共享数据的时候·需要进行同步控制
线程同步能够保证多个线程安全访问竞争资源·最简单的同步机制是引入互斥锁
互斥锁为资源引入一个状态:锁定/非锁定
某个线程要更改共享数据时·先行其锁定·此时资源的状态为“锁定“·其他线程不能更改:直到该线程释
放资源,符资源的状态变成“非锁定“·其他的线程才能再次锁定该资源·互斥锁保证了每次只有一个线程
进行写入操作·从而保证了多线程情况下数据的正确性

而threading中定义了Lock类,可以方便的处理锁定:

#创建锁
mutex = threading.Lock()

# 锁定
mutex.acquire()

# 释放
mutex.release()

注意:
- 如果这个锁之前是没有上锁的,那么acquire不会堵塞
- 如果在调用acquire对这个锁上锁之前,他已经被其他线程上锁,那么此时acquire()会堵塞,直到被解锁为止。

# 定义一个全局变量
g_num = 0
def test1(num):
    lobal g_num
    # 上锁,如果之前没有被上锁,那么此时 上锁成功
    # 如果之前上锁了,此时堵塞,直到锁被解开例如:有三个客户C1,c2,c3,向银行家借款,该银行家的资我总额为10个资金单位,其中C1客户要借
9各资金单位,C2客户要借3个资金单位,C3客户要借8个资金单位,总计20个资金单位。某一时刻的状态

    # mutex.acquire() 写在这里,特么和单线程有啥区别,test1执行完了你才解锁,全程test2就在等你test1执行
    for i in rage(num):
        mutex.acquire()
        g_num += 1
        # 解锁
        mutex.release()
    # mutex.release()
    
    print("---in test1 g_num=%d---"%g_num)
    
def test2(num):
    lobal g_num
    mutex.acquire()
    for i in rage(num):
        g_num += 1
    mutex.release()
    print("---in test2 g_num=%d---"%g_num)
    
# 创建一个互斥锁,默认是没有上锁的
mutex = threading.Lock()
    
def main():
    t1 threading Thread(target=test1, args=(100000,))
    t2 threading Thread(target=test2, args=(100000,))
    t1. start()
    t2. start()
    #等待上面的?个线程执行完
    time. sleep(5)
    print("---in main g_num=%d---"%g_num)

if __name__ == "__main__":
    main()

死锁

class MyThread1(threading.Thread):
    def run(self):
        # 对mutexA上锁
        mutexA.acquire()
        
        # mutexA上锁后,延时1秒,等待另外哪个线程把mutexB上锁
        print(self.name+'___do1--up__')
        time.sleep(1)
        
        # 此时会阻塞,因为这个mutexB已经被另外的线程抢先上锁了,
        mutexB.acquire()
        print(self.name+'___do1--down__')
        mutexB.release()
        
        # 对mutexA解锁
        mutexA.release()
        
class MyThread2(threading.Thread):
    def run(self):
        # 对mutexB上锁
        mutexB.acquire()
        
        # mutexB上锁后,延时1秒,等待另外哪个线程把mutexB上锁
        print(self.name+'___do2--up__')
        time.sleep(1)
        
        # 此时会阻塞,因为这个mutexA已经被另外的线程抢先上锁了,
        mutexA.acquire()
        print(self.name+'___do2--down__')
        mutexA.release()
        
        # 对mutexB解锁
        mutexB.release()

进程 程序的概念

进程,你就理解成,电脑上的QQ打开,他就是一个进程。 你的QQ里面能够能打开很多窗口,这个窗口就是线程,
创建进程

import threading
import time
import multiprocessing

def test1():
    while True():
        print("1----")
        time.sleep(1)
        
def test2():
    while True:
        print("2-----")
        time.sleep(1)
        
def main():
    p1 = multiprocessing.Process(target = test1)
    p2 = multiprocessing.Process(target = test2)
    p1.start()
    p2.start()
    
if __name__ == "__main__":
    main()

进程间的通信-Queue

  1. Queue的使用
    可以使用mulitiprocessing模块的Queue实现多进程之间的数据传输,Queue本身是一个消息队列程序,首先用一个小示例来延时一下Queue的工作原理:
from multiprocessing import Queue
q = Queue(3) #初始化一个queue对象,最多可以接收三条put消息
q.put("消息1")
q.put("消息2")
print(q.full()) # False
q.put("消息3")
print(q.full()) #True

# 因为消息队列已经满了,try都会弹出一场,第一个try会等待2秒后再抛出异常,第二个立即抛出
try:
    q.put("消息4",True,2)
except:
    print("消息队列已满,现有消息数量:%s"%q.qsize())
    
try:
    q.put_nowait("消息4")
except:
    print("消息队列已满,现有消息数量:%s"%q.qsize())


# 一下的东西都自己去试试吧
q.put("消息4")
# 这种放入,因为队列是满的 他会阻塞等队列空出来

# 和put的性质差不多,但是这个是取值。
q.get()
q.get_nowait()

应用:

import multiprocessing

def download_from_web(q):
    """ 下载数据 """
    data = [11,22,33,44]
    # 向队列中写入数据
    for temp in data:
        q.put(temp)
        
def analysis_data(q):
    """ 数据处理 """
    waitting_analysis_data = list()
    while True:
        data = q.get()
        waitting_analysis_data.append(data)
        
        if q.empty():
            break
def main():
    # 1. 创建一个队列
    q = multiprocessing.Queue()
    
    # 2. 创建多个进程,将队列的引用当作实参进行传递到里面
    p1 = multiprocessing.Process(target = download_from_web,args=(q,))
    p2 = multiprocessing.Process(target = download_from_web,args=(q,))
    p1.start()
    p2.start()

进程池

当需要创建的子进程数量不多时·可以直接利用 multiprocessing中的Process动态成生多个进程·但如果
是上百甚至上千个目标·手动的去创建进程的工作量巨大·此时就可以用
multiprocessing块提供的Pool方法
初始化Pool时·可以指定一个最大进程数·当有新的请求交到Pool中时·如果池还没有满那么就会创
建一个新的进程用来执行该请求:但如果池中的进程数已经达到指定的最大值·那么该请求就会等待·直
到池中有进程结束·才会用之前的进程来执行新的任务·请看下面的实例

from multiprocessing import Pool
import os,time,random

def worker(msg):
    t_start = time.time()
    print("开始执行,进程为%d"%(msg,os.getpid()))
    # random.random() 随机生成0~1之间的浮点数
    time.sleep(random.random()*2)
    t_stop = time.time()
    print(msg,"执行完毕,耗时%0.2f"%(t_stop-t_start))

po = Pool(3) # 定义一个进程池,醉倒进程数为3
for i in range(0,10):
    # Pool().apply_async(要调用的目标,(传递给目标参数元组,))
    po.apply_async(worker,(i,))
    
print("-----start-----")
po.close()
# 关闭进程池,关闭后po不再接收新的请求
po.join() # 等待po中所有子进程执行完毕,必须放在close语句之后,没这句话主进程就直接死了。这个算一个插队吧,把进程池创建的进程插队到主进程这里来,让主进程等子进程执行完了再死。
print("---end----")

协程

迭代器

  1. 可迭代对象
    我们已经知道可以对list、tuple、str等类型的数据使用for…in…的循环语法从其中依次拿到数据进行使用,我们把这样的过程称为遍历,也叫迭代。

但是,是否所有的数据类型都可以放到for…in…的语句中,然后让for…in…每次从中取出一条数据供我们使用,鸡供我们迭代吗?

from collections import Iterable
from collections import Iterator


# 看[1,2,3]是不是iterable类型的
isinstance([1,2,3],Iterable)
  1. 自己创建一个可迭代对象
from collections import Iterable
from collections import Iterator
class Classmate(object):
    def __init__(self):
        self.names = list()
    
    def add(self,name):
        self.names.append(name)
        
    def __iter__(self):
        """ 如果想要一个对象称为一个可以迭代的对象,即可以使用for,那么必须实现__iter__方法 
        但是如果只有这个,我们是不是差一个可以记录你每一次取值的东西,所以需要__iter__返回一个具有__iter__方法和__next
        __方法的引用。
        """
        return ClassIterator(self)
        
class ClassIterator(object):
    def __init__(self,obj):
        self.obj = obj
        self.currentnum = 0

    def __iter(self):
        pass
    
    def __next__(self):
        if self.current_num<len(self.obj.names):
            ret = self.obj.names[self.current_num]
            self.current_num += 1
            return ret
        else:
            raise StopIteration

classmate=Classmate()

classmate.add("老王")
classmate.add("王二")
classmate.add("张三") 

print(isinstance(classmate,Iterable))

classmate_iterator = iter(classmate)
print(isinstance(classmate_iterator,Iterator))

print(next(classmate_iterator))

两个类方便理解,下面整合成一个类

class Classmate(object):
    def __init__(self):
        self.names = list()
        self.current_num = 0
    
    def add(self,name):
        self.names.append(name)
        
    def __iter__(self):
        return self
        
    def __next__(self):
        if self.current_num<len(self.names):
            ret = self.names[self.current_num]
            self.current_num += 1
            return ret
        else:
            raise StopIteration

使用for循环的条件

  1. 判断xxx_obj是否是可以迭代的
  2. 再第一步成立的前提下,调用iter函数得到xxx_obj对象的__iter__方法的返回值。
  3. __iter__方法的返回值是一个迭代器。
    这里说一下,并不是只有foi循环能接收可迭代对象,强转类型基本都用到了可迭代对象

生成器

利用迭代器·我们可以在每次迭代获取数据(通过next方法)时按照特定的规律进行生成·但是我们在实
现一个迭代器时·关于当前迭代到的状态需要我们自己记录·进而才能根据当前状态生成下一个数据·为
了达到记录当前状态·并配合next()函进行选代使用·我们可以采用更简便的语法·即生成器
generator)·生成器是一类特殊的迭代器

这里有点意思,我第一学到列表生成器之后,紧接着学到了元组,我寻思是不是也有个和列表生成器一样的东西来生成元组

l = [x for x in range(10)]
==>>
l = (x for x in range(10))

结果发现,特么打印出来的是个什么鬼东西,我完全不认识啊,后面慢慢学习的时候才发现,这个玩意就是生成器

当然上面的方法,在真正运用到生成器的时候 运用的比较少,下面才是真正的用法

def fib(n):
    current = 0
    num1,num2 = 0,1
    while current < n:
        num = num1
        num1, num2 = num2,num1+num2
        current += 1
        yield num
    return "done"

注意到有个yield函数里面有yield语句就是个生成器的模板。如果调用fib时,它有yield,不是调用函数,而是创建一个生成器对象。
一定要注意 yield 它是个特殊的迭代器,可以直接通过next()取值。

通过send调用传参

def fib(n):
    a,b = 0,1
    current_num = 0
    while current_num < all_num:
        ret = yield a
        print(ret)
        a,b = b,a+b
        current_num += 1

obj = create_num(10)

ret = obj.send("hah")
print(ret)

send会在yeild暂停之后,第二次启动的时候执行上面的赋值语句,把’hah’赋值给ret

使用yield完成多任务

improt time

def task_1():
    while True:
        print("---1---")
        time.sleep(0.1)
        yield

def task_2():
    while True:
        print("---2---")
        time.sleep(0.1)
        yield
        
def main():
    t1 = task_1()
    t2 = task_2()
    while True:
        next(t1)
        next(t2)
if __name__ == "__main__":
    main()

安装 greenlet

pip3 install greenlet
import greenlet import greenlet
import time

def task_1():
    while True:
        print("---1---")
        gr2.switch()
        time.sleep(0.5)

def task_2():
    while True:
        print("---2---")
        gr1.switch()
        time.sleep(0.5)
        
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()

以上基本实现了协程,但是这个得自己代码切换,有点麻烦gevent直接来统一给你管理。

import gevent

def f(n):
    for i in range(n):
        print(gevent.getcurrent(),i)
        # 使用gevent的阻塞 比如socket里面的阻塞都给爷换成这个,是不是很蠢嘻嘻,所以下面讲到打补丁
        gevent.sleep(0.5)
# spawn 下了个卵(卵的执行函数,参数)不是我恶趣味叫他卵哈,特么这玩意真的翻译是个卵
g1 = gevent.spawn(f,5)
g2 = gevent.spawn(f,5)
g3 = gevent.spawn(f,5)
g1.join()
g2.join()
g3.join()
打补丁
import gevent

#打上这个补丁之后,就普通阻塞操作变成了gevent的阻塞
monkey.path_all()

def f(n):
    for i in range(n):
        print(gevent.getcurrent(),i)
        time.sleep(0.5)
#g1 = gevent.spawn(f,5)
#g2 = gevent.spawn(f,5)
#g3 = gevent.spawn(f,5)
#g1.join()
#g2.join()
#g3.join()
gevent.joinall([
    gevent.spawn(f,5),
    gevent.spawn(f,5)
])

简单总结

1.进程是资源分配的单位
2.线程是操作系统调度的单位
3.进程切换需要的资源很最大·效率很低
4.线程切换需要的资源一般·效率一般(当然了在不考虑GIL的情况下)
5.协程切换任务资源很小·效率高
6.多进程·多线程根据cpu核数不一样可能是并行的·但是协程是在一个线程中所以是并发

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值