Bootstrap

python老男孩汇总笔记(多任务,进程、线程、协程)

多任务

当没有多任务时

import time



def sing():
    """唱歌5秒"""
    for i in range(5):
        print("唱歌中...")
        time.sleep(1)
        
def dance():
    """跳舞5秒"""
    for i in range(5):
        print("跳舞中")
        time.sleep(1)
        

sing()
dance()
# 唱歌中...
# 唱歌中...
# 唱歌中...
# 唱歌中...
# 唱歌中...
# 跳舞中
# 跳舞中
# 跳舞中
# 跳舞中
# 跳舞中

上面的代码一共运行十秒,先是唱歌,然后再跳舞,这样不符合我们实际的需求,所以我们要做修改。我们将代码使用多任务

import time
import threading



def sing():
    """唱歌5秒"""
    for i in range(5):
        print("唱歌中...")
        time.sleep(1)
        
def dance():
    """跳舞5秒"""
    for i in range(5):
        print("跳舞中...")
        time.sleep(1)
        
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
# 唱歌中...跳舞中...

# 唱歌中...跳舞中...

# 跳舞中...唱歌中...

# 跳舞中...唱歌中...

# 跳舞中...
# 唱歌中...

多任务就是操作系统可以同时运行多个任务,也就是,我们在PC端,一边浏览网页一边听歌,一边写报表

  • 并行:真的多任务
    并发:假的多任务

线程

线程
线程是操作系统能够进行运算调度的最小单位,他被包含在进程中,是进程中的实际运作单位,
一条线程指的是进程中一个单一顺序的控制流,一个进程可以后多个并发的线程,每条线程并行执行不同的任务
线程是一串质量的集合
所有在同一个进程里的线程是共享同一块内存空间

进程和线程的区别

  1. 线程共享内存空间,进程的内存是独立的
  2. 同一进程的线程之间可以直接交流,两个进程想通信,必须通过一个中间代理。
  3. 创建新线程很简单,创建新进程需要对父进程进行一次克隆
  4. 一个线程可以控制和同一进程的其他线程,但是进程只能操作子进程
    最简单的线程
import threading

def run(n):
    print("task", n)


t1 = threading.Thread(target=run,args=("t1",))
t2 = threading.Thread(target=run,args=("t2",))
t1.start() # 启动线程
t2.start()

一个程序运行起来之后,一定有一个执行代码的东西,这个东西我们称之为线程
红色的是主线程,其他颜色是子线程,当start开始的时候子线程开始启动。
而且主线程会等待子线程运行结束。
在这里插入图片描述


回忆函数改变局部变量

'''
定义一个全局变量n在调用函数改变,打印两次结果
'''
n = 1


def test(n):
    n += 1
    print("函数内部 %s" %n)

def test2():
    global n
    n += 1


'''
全局变量
'''    
print(n)
print(id(n))

'''
调用函数
'''
test(n)
print(n)
print(id(n))

'''
调用全局变量的函数
'''
test2()
print(n)
print(id(n))

# 1
# 140732203311504
# 函数内部 2
# 1
# 140732203311504
# 2
# 140732203311536
 	          

在一个函数中,对局部变量进行修改的时global的使用方法

  • 如果修改了指向,即让全局变量指向了一个新的地方,那么必须使用global
  • 如果仅仅只是修改了空间中的数据,不用global
    验证线程之间共享
import threading
import time


n = 100
'''
让n产生变化
'''
def test1():
    global n
    n += 1
    print("----in test1 n=%d" % n)
    
'''
验证这个线程是否共享数据
'''
def test2():
    print("---in test2 n = %d" % n)
    
t1 = threading.Thread(target=test1)
t2 = threading.Thread(target=test2)

t1.start()
time.sleep(1) # 为了让test1先执行完毕

t2.start()
time.sleep(1)
print("---in main Thread n = %d---" % n)


结果

----in test1 n=101
---in test2 n = 101
---in main Thread n = 101---

初识资源竞争

import threading
import time

# 定义一个全局变量

n = 0

def test1(num):
    global n
    for i in range(num):
        n += 1
    print('----test1----%d' % n)
        
        
def test2(num):
    global n
    for i in range(num):
        n += 1
    print('----test2----%d' % n)
        
t1 = threading.Thread(target=test1, args=(100000,))
t2 = threading.Thread(target=test2, args=(100000,))

t1.start()
t2.start()


# 等待上面的2个线程执行
time.sleep(0.1)

print('---in main Thread n=%d---' %n)
----test1----100000
----test2----197686
---in main Thread n=197686---

互斥锁

当多个线程几乎同时修改某一个共享数据时,可能出现数据错乱,所以我们还要引入互斥锁状态为资源上锁。
当某个线程要修改共享的数据时,先将其锁定称为锁定状态,其他线程无法更改,直达该线程释放资源,其他线程才可以进行修改。

# 创建锁
mutex = threading.Lock()

# 锁定
mutex.acquire()

# 释放
mutex.release()
import threading
import time

# 定义一个全局变量

n = 0

def test1(num):
    global n
    # 上锁,如果之前没有上锁,那么上锁成功
    # 如果上锁之前已经被上锁了,那么此时会堵塞在这里,直到这个锁被解开
    mutex.acquire()
    for i in range(num):
        n += 1
    mutex.release()
    print('----test1----%d' % n)
        
        
def test2(num):
    global n
    mutex.acquire()
    for i in range(num):
        n += 1
    mutex.release()
    print('----test2----%d' % n)

# 创建一个互斥锁,默认是没有上锁的
mutex = threading.Lock()
        
t1 = threading.Thread(target=test1, args=(100000,))
t2 = threading.Thread(target=test2, args=(100000,))

t1.start()
t2.start()


# 等待上面的2个线程执行
time.sleep(0.1)

print('---in main Thread n=%d---' %n)
----test1----100000----test2----200000

---in main Thread n=200000---

死锁

在线程间共享多个资源的时候,如果两个线程分别占有一部分资源而且同时等待对方的资源,就会造成死锁

事件

import threading

event_test = threading.Event()


# event 初试为false
# event.set()方法将其设置为True
# event.clear()方法将其设置为false
# event.wait()方法将其堵塞,一直到TRUE才会解堵塞


# 判断事件为false或者True
print(event_test.is_set()) # 可以验证初试时为false

event_test.set()
print(event_test.is_set()) # 可以验证修改后为true

event_test.clear()
print(event_test.is_set()) # 可以验证修改后为false
import threading
import time

# 全局变量事件
event_test = threading.Event()

def test():
    '''
    模仿设备运行,5秒钟后出现故障,一秒修复
    '''
    count = 1
    event_test.set()
    while True:
        if count > 5 and count < 7:
            event_test.clear() # 清空标志位
            print("警告", count)
            
        elif count >= 7:
            event_test.set()
            print("解决中",count)
            count = 0
        else:
            
            print("正常",count)
            
        time.sleep(1)
        
        count += 1
        
        
def server(name):
     while True:
        if event_test.is_set(): 
            print("%s is runing " % name )
            time.sleep(1)
        else:
            print('%s is waitting ' % name)
            event_test.wait()
            print("%s wait..." % name)
test1 = threading.Thread(target=test,)
test1.start()

server1 = threading.Thread(target=server, args=('Ruijie',))
server1.start()    


python多线程,不适合cup密集操作的任务,适合io操作密集型的任务

进程

进程是资源的集合,进程至少有一个线程
最简单的进程

import multiprocessing
import time


def run(name):
    time.sleep(2)
    print('hello', name)
    
    
if __name__ == '__main__':
    for i in range(10):
        p = multiprocessing.Process(target=run, args=('bob%s' % i,))
        p.start()

查看进程ID,确认所有的进程都是由一个父进程创建

from multiprocessing import Process
import os


def info(title):
    print(title)
    print('module name:', __name__)
    print('parent process:', os.getppid())  # 获取父进程id
    print('process id:', os.getpid())  # 获取子进程id
    print('\n\n')


def f(name):
    info('function f')
    print('hello', name)


if __name__ == '__main__':
    info('main process line')
    p = Process(target=f, args=('vv', ))
    p.start()
    p.join()


进程之间用队列通信

# 不同进程间内存是不共享的,所以我们要通过别的方法实现
# 1. Queues(实现子进程和主进程之间的通信)

from multiprocessing import Process, Queue


def f(q):
    q.put([1, 2, 3])


if __name__ == "__main__":
    q = Queue()  # 主进程
    p = Process(target=f, args=(q,))  # 子进程
    p.start()
    print(q.get())
    p.join()
管道传递消息
from multiprocessing import Process, Pipe

def f(conn):
    conn.send([1, 2, 3])  # 发送进程传递的消息
    conn.close()


if __name__ == "__main__":
    parent_conn, child_conn = Pipe()
    p = Process(target=f, args=(child_conn,))
    p.start()
    print(parent_conn.recv())  # 接收进程传递的消息
    p.join()
manage数据共享
def f(l):
    l.append(os.getpid())
    print(l)


if __name__ == '__main__':
    with Manager() as manager:

        l = manager.list(range(5))  # 生成一个可以进行多进程之间的数据共享列表
        p_list = []

        for i in range(10):
            p = Process(target=f, args=(l,))
            p.start()
            p_list.append(p)
            for res in p_list:
                res.join()
            print(l)

进程锁

from multiprocessing import Process, Lock


def f(l,i):
    l.acquire()

    print('hello world', i)

    l.release()

if __name__ == '__main__':
    lock = Lock()

    for num in  range(10):
        Process(target=f, args=(lock, num)).start()


不加进程锁会出现的问题:
可能会导致屏幕混乱
在这里插入图片描述

进程池

from multiprocessing import Process, Pool
import time
import os


def Foo(i):
    time.sleep(2)
    print("in process", os.getpid())


def Bar(arg):
    print("-->exec done", arg, os.getpid())


if __name__ == '__main__':

    pool = Pool(processes=3)  # 运行进程池同时有5个进程
    print("主进程:", os.getpid())
    for i in range(10):
        pool.apply_async(func=Foo, args=(i,), callback=Bar)  # 并行,callback回调,韩式执行完func后执行callback函数
        # pool.apply(func=Foo, args=(i,))  # 串行

    print("end")
    pool.close()
    pool.join()  # 进程池中进程执行完毕后再关闭

协程

迭代器

迭代器
迭代是访问集合元素的一种方式,迭代器是一个可以记住遍历的位置的对象,
迭代器对象从集合的第一个元素开始访问,直到所有的元素访问完结束,迭代器只能往前不会后台
目前迭代的可以是列表,字典元祖。

# 可迭代对象
for temp in [1, 2, 3]:
    print(temp)

for temp in 'abcde':
    print(temp)

我们自己定义一个类去迭代自己想打印的东西

class Book(object):
    def __init__(self):
        self.names = list()

    def add(self, name):
        self.names.append(name)


book = Book()

book.add('语文')
book.add('数学')
book.add('英语')

# 我们想迭代打印我们添加的课本
for name in book:
    print(name)

# TypeError: 'Book' object is not iterable
# 被告知无法迭代

可以迭代的类

class Book(object):
    def __init__(self):
        self.names = list()
        self.current_num = 0

    def add(self, name):
        self.names.append(name)

    # 实现迭代效果必须满足两个条件:
    # 1.是否可以迭代
    # 2.调用__iter__函数得到对象的__next__方法的返回值
    # 3.__iter__方法的返回值是一个迭代器
    def __iter__(self):
        """如果想要一个对象称之为可迭代对象,即可以使用for,那么必须实现__iter__方法"""
        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


book = Book()

book.add('语文')
book.add('数学')
book.add('英语')

# 我们想迭代打印我们添加的课本
for name in book:
    print(name)

# TypeError: 'Book' object is not iterable
# 被告知无法迭代

迭代器储存的是形成数据的方式而不是数据本身

我们发现迭代器最核心的就是可以通过next()函数的调用来返回下一个数据值,如果每次返回的数据值不是一个已有的数据集合中读取的,而是通过按照一定规律计算生成的,那么就意味着可以不用再以来一个已有的数据集合,也就是说不用再将所有的数据一次性缓存下来供后续一次读取,这样可以节约大量空间。

# 普通的裴波那序列
nums = list()

a = 0
b = 1
i = 0
while i < 10:
    nums.append(a)
    a, b = b, a+b
    i += 1
    
for num in nums:
    print(num)

迭代器的斐波拉契

# 迭代器的斐波拉契
import time



class Fibonacci(object):
    def __init__(self, all_num):
        self.all_num = all_num
        self.current_num = 0
        self.a = 0
        self.b = 1
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.current_num < self.all_num:
            ret = self.a
            
            self.a, self.b = self.b, self.a + self.b
            self.current_num += 1
            
            return ret
        else:
            raise StopIteration


start = time.time()            
fibo = Fibonacci(100)

for num in fibo:
    print(num)
    
end = time.time()

print(end-start)

从这两个案例可以看出用迭代器的更加快

生成器

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

生成器的第一种方式
在这里插入图片描述将推导式的中括号换成小括号

第二种方式,含有yield的函数

# 函数的fib序列

def fib(nums):
    a, b = 0, 1
    current_num = 0
    while current_num < nums:
        print(a)
        a, b = b, a+b
        current_num += 1
        
        
fib(10)

利用yield形成生成器

# 函数的fib序列

def fib(nums):
    a, b = 0, 1
    current_num = 0
    while current_num < nums:
        # print(a)
        yield a  # 如果一个函数中有yield语句,就不是函数,而是一个生成器的模板,暂停功能,函数下次从这里开始运行
        a, b = b, a+b
        current_num += 1
        
# 如果在调用fib的时候,发现这个函数中有yield那么此时,不是调用函数,而是创建一个生成器对象        
obj = fib(10)

for num in obj:
    print(num)

关于yield的暂停测试

# 函数的fib序列

def fib(nums):
    print('-----1----')
    a, b = 0, 1
    current_num = 0
    while current_num < nums:
        print('----2-----')
        # print(a)
        yield a  # 如果一个函数中有yield语句,就不是函数,而是一个生成器的模板,暂停功能,函数下次从这里开始运行
        print('------3------')
        a, b = b, a+b
        current_num += 1
        print('-----4-----')
# 如果在调用fib的时候,发现这个函数中有yield那么此时,不是调用函数,而是创建一个生成器对象        
obj = fib(10)  # obj是生成器对象
ret = next(obj)
print(ret)


ret = next(obj)
print(ret)

ret = next(obj)
print(ret)

代码结果

-----1----
----2-----
0
------3------
-----4-----
----2-----
1
------3------
-----4-----
----2-----
1

从上面测试可以看出,当代码执行到yield时会暂停,当再次执行next()时从这里恢复开始。
yield 关键字有两点作用

  1. 保存当前运行的状态,然后暂停执行,即将生成器挂起
  2. 将yield关键字后面表达式的值作为返回值返回

当循环超过设定的值可以用异常抛出

def create_num(all_num):
    a, b = 0, 1
    current_num = 0
    while current_num < all_num:
        yield a 
        a, b = b, a+b
        current_num += 1
        
obj = create_num(10)

while True:
    try:
        ret = next(obj)
        print(ret)
    except Exception as ret:
        break

迭代器函数有返回值

def create_num(all_num):
    a, b = 0, 1
    current_num = 0
    while current_num < all_num:
        yield a 
        a, b = b, a+b
        current_num += 1
    return 'aaaaaa'  # 倘若 函数有一个返回值
        
obj = create_num(10)

while True:
    try:
        ret = next(obj)
        print(ret)
    except Exception as ret:
        print(ret.value)  # 这里可以提取
        break


send

通过send来启动生成器并且可以通过传递参数控制生成器运行。

def create_num(all_num):
    a, b = 0, 1
    current_num = 0
    while current_num < all_num:
        ret = yield a
        print(".>>>ret", ret)
        if ret != None:
            a = ret
        a, b = b, a+b
        current_num += 1
    return 'aaaaaa'  # 倘若 函数有一个返回值
        
obj = create_num(10)

ret = next(obj)
print(ret)

ret = obj.send(55)  # 可以传递一个参数,控制生成器的运行结果
print(ret)

while True:
    try:
        ret = next(obj)
        print(ret)
    except Exception as ret:
        break

结果:

0
.>>>ret 55
1
.>>>ret None
56
.>>>ret None
57
.>>>ret None
113
.>>>ret None
170
.>>>ret None
283
.>>>ret None
453
.>>>ret None
736
.>>>ret None
1189
.>>>ret None

最简单的用yield实现多任务

import time


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

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

用gevent实现多任务

import gevent


def f1(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        gevent.sleep(0.5)  # 延时切换
        
def f2(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        gevent.sleep(0.5)



g1 = gevent.spawn(f1, 5)
g2 = gevent.spawn(f2, 5)

g1.join()  # 等待协程结束,当遇到延时时会自动切换
g2.join()

结果:

Greenlet at 0x22959894bf8: f1(5)> 0
<Greenlet at 0x229599f3378: f2(5)> 0
<Greenlet at 0x22959894bf8: f1(5)> 1
<Greenlet at 0x229599f3378: f2(5)> 1
<Greenlet at 0x22959894bf8: f1(5)> 2
<Greenlet at 0x229599f3378: f2(5)> 2
<Greenlet at 0x22959894bf8: f1(5)> 3
<Greenlet at 0x229599f3378: f2(5)> 3
<Greenlet at 0x22959894bf8: f1(5)> 4
<Greenlet at 0x229599f3378: f2(5)> 4
;