【python进阶攻略13】协程、内存copy、多进程

协程

Python中的协程和生成器很相似但又稍有不同。主要区别在于:

  • 生成器是数据的生产者
  • 协程则是数据的消费者

首先我们先来回顾下生成器的创建过程。我们可以这样去创建一个生成器:

    def fib():
        a, b = 0, 1
        while True:
            yield a
            a, b = b, a+b

然后我们经常在for循环中这样使用它:

    for i in fib():
        print i

这样做不仅快而且不会给内存带来压力,因为我们所需要的值都是动态生成的而不是将他们存储在一个列表中。更概括的说如果现在我们在上面的例子中使用yield便可获得了一个协程。协程会消费掉发送给它的值。Python实现的grep就是个很好的例子:

    def grep(pattern):
        print("Searching for", pattern)
        while True:
            line = (yield)
            if pattern in line:
                print(line) 

等等!yield返回了什么?啊哈,我们已经把它变成了一个协程。它将不再包含任何初始值,相反要从外部传值给它。我们可以通过send()方法向它传值。这有个例子:

    search = grep('coroutine')
    next(search)
    #output: Searching for coroutine
    search.send("I love you")
    search.send("Don't you love me?")
    search.send("I love coroutine instead!")
    #output: I love coroutine instead!

发送的值会被yield接收。我们为什么要运行next()方法呢?这样做正是为了启动一个协程。就像协程中包含的生成器并不是立刻执行,而是通过next()方法来响应send()方法。因此,你必须通过next()方法来执行yield表达式。

我们可以通过调用close()方法来关闭一个协程。像这样:

    search = grep('coroutine')
    search.close()

更多协程相关知识的学习大家可以参考David Beazley的这份精彩演讲

内存copy

id

什么是id?一个对象的id值在CPython解释器里就代表它在内存中的`地址

>>> import copy
>>> a=[1,2,3]
>>> b=a
>>> id(a)
"""
4382960392
"""
>>> id(b)
"""
4382960392
"""
>>> id(a)==id(b)    #附值后,两者的id相同,为true。
True
>>> b[0]=222222  #此时,改变b的第一个值,也会导致a值改变。
>>> print(a,b)
[222222, 2, 3] [222222, 2, 3] #a,b值同时改变

##浅拷贝

当使用浅拷贝时,python只是拷贝了最外围的对象本身,内部的元素都只是拷贝了一个引用而已。看代码:

>>> import copy
>>> a=[1,2,3]
>>> c=copy.copy(a)  #拷贝了a的外围对象本身,
>>> id(c)
4383658568
>>> print(id(a)==id(c))  #id 改变 为false
False
>>> c[1]=22222   #此时,我去改变c的第二个值时,a不会被改变。
>>> print(a,c)
[1, 2, 3] [1, 22222, 3] #a值不变,c的第二个值变了,这就是copy和‘==’的不同

##深拷贝

deepcopy对外围和内部元素都进行了拷贝对象本身,而不是对象的引用。

#copy.copy()

>>> a=[1,2,[3,4]]  #第三个值为列表[3,4],即内部元素
>>> d=copy.copy(a) #浅拷贝a中的[3,4]内部元素的引用,非内部元素对象的本身
>>> id(a)==id(d)
False
>>> id(a[2])==id(d[2])
True
>>> a[2][0]=3333  #改变a中内部原属列表中的第一个值
>>> d             #这时d中的列表元素也会被改变
[1, 2, [3333, 4]]


#copy.deepcopy()

>>> e=copy.deepcopy(a) #e为深拷贝了a
>>> a[2][0]=333 #改变a中内部元素列表第一个的值
>>> e
[1, 2, [3333, 4]] #因为时深拷贝,这时e中内部元素[]列表的值不会因为a中的值改变而改变
>>>

多进程

本节我们来学习threading模块的一些基本操作,如获取线程数,添加线程等。首先别忘了导入模块:

import threading
获取已激活的线程数
threading.active_count()

查看所有线程信息

threading.enumerate()
# [<_MainThread(MainThread, started 140736011932608)>, <Thread(SockThread, started daemon 123145376751616)>]
输出的结果是一个<_MainThread(...)>带多个<Thread(...)>。

查看现在正在运行的线程

threading.current_thread()
# <_MainThread(MainThread, started 140736011932608)>

添加线程,threading.Thread()接收参数target代表这个线程要完成的任务,需自行定义

def thread_job():
    print('This is a thread of %s' % threading.current_thread())

def main():
    thread = threading.Thread(target=thread_job,)   # 定义线程 
    thread.start()  # 让线程开始工作
    
if __name__ == '__main__':
    main()

join

我们让 T1 线程工作的耗时增加.

import threading
import time

def thread_job():
    print("T1 start\n")
    for i in range(10):
        time.sleep(0.1) # 任务间隔0.1s
    print("T1 finish\n")

added_thread = threading.Thread(target=thread_job, name='T1')
added_thread.start()
print("all done\n")

预想中输出的结果是否为:

T1 start
T1 finish
all done

但实际却是:

T1 start
all done
T1 finish

线程任务还未完成便输出all done。如果要遵循顺序,可以在启动线程后对它调用join:

added_thread.start()
added_thread.join()
print("all done\n")

使用join对控制多个线程的执行顺序非常关键。举个例子,假设我们现在再加一个线程T2,T2的任务量较小,会比T1更快完成:

def T1_job():
    print("T1 start\n")
    for i in range(10):
        time.sleep(0.1)
    print("T1 finish\n")

def T2_job():
    print("T2 start\n")
    print("T2 finish\n")

thread_1 = threading.Thread(target=T1_job, name='T1')
thread_2 = threading.Thread(target=T2_job, name='T2')
thread_1.start() # 开启T1
thread_2.start() # 开启T2
print("all done\n")

输出的”一种”结果是:

T1 start
T2 start
T2 finish
all done
T1 finish

现在T1和T2都没有join,注意这里说”一种”是因为all done的出现完全取决于两个线程的执行速度, 完全有可能T2 finish出现在all done之后。这种杂乱的执行方式是我们不能忍受的,因此要使用join加以控制。

我们试试在T1启动后,T2启动前加上thread_1.join():

thread_1.start()
thread_1.join() # notice the difference!
thread_2.start()
print("all done\n")

输出结果:

T1 start
T1 finish
T2 start
all done
T2 finish

可以看到,T2会等待T1结束后才开始运行。

如果我们在T2启动后放上thread_1.join()会怎么样呢?

thread_1.start()
thread_2.start()
thread_1.join() # notice the difference!
print("all done\n")

输出结果:

T1 start
T2 start
T2 finish
T1 finish
all done

T2在T1之后启动,并且因为T2任务量小会在T1之前完成;而T1也因为加了join,all done在它完成后才显示。

你也可以添加thread_2.join()进行尝试,但为了规避不必要的麻烦,推荐如下这种1221的V型排布:

thread_1.start() # start T1
thread_2.start() # start T2
thread_2.join() # join for T2
thread_1.join() # join for T1
print("all done\n")

"""
T1 start
T2 start
T2 finish
T1 finish
all done
"""

##储存进程结果

import threading
import time
from queue import Queue

def job(l,q):
    for i in range(len(l)):
        l[i] = l[i]**2
    q.put(l)

def multithreading():
    q = Queue()
    threads = []
    data = [[1,2,3],[3,4,5],[4,4,4],[5,5,5]]
    for i in range(4):
        t = threading.Thread(target=job, args=(data[i], q))
        t.start()
        threads.append(t)
    for thread in threads:
        thread.join()
    results = []
    for _ in range(4):
          results.append(q.get())
    print(results)

if __name__ == '__main__':
    multithreading()

多线程的lock

类似golang里面的lock,推荐大家用协程,或者多进程解决这个问题。

import threading

def job1():
    global A, lock
    lock.acquire()
    for i in range(10):
        A += 1
        print('job1', A)
    lock.release()

def job2():
    global A, lock
    lock.acquire()
    for i in range(10):
        A += 10
        print('job2', A)
    lock.release()

if __name__ == '__main__':
    lock = threading.Lock()
    A = 0
    t1 = threading.Thread(target=job1)
    t2 = threading.Thread(target=job2)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水木流年追梦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值