线程、进程、迭代器、生成器、yield协程

简要介绍一下操作系统

操作系统承上启下,管理硬件,对上提供接口。
没有一个绝对的定义,
操作系统是一个用来协调、管理和控制计算机硬件和软件资源的系统程序,它位于硬件和软件之间。

操作系统的发展:
穿孔卡片——》晶体管和批处理系统,就是一批数据输入和一批数据输出
多道程序设计,就是当IO阻塞时切换任务——》分时操作系统:多个联机终端+多道技术
现在就是个人计算机了

对于进程和线程

进程:本质上就是一段程序的运行过程(抽象概念)
线程:最小的执行单元(实例)
进程:最小的资源单位


进程,就是CPU高速切换,对状态的保存和切换

进程内部开启线程,共享一套数据(这个是重点),就是线程,线程可以实现在进程内并发

1. 一个程序至少有一个进程,一个进程至少有一个线程(进程可以理解成线程的容器)。
2. 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
3. 线程在执行过程中与进程还是有区别的,每个独立的线程有一个程序运行的的入口、
    顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,
    由应用程序提供多个线程执行控制。
4.进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,
    进程是系统进行资源分配和调度的一个独立单位,
5.一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。

那什么是并发、并行呢?

并发:是指系统具有 处理多个任务(动作)的能力
并行:是指系统具有 同时处理多个(动作)的能力

并行相对于并发多了“同时”这两个字,强调的是同一个时间点去执行,而不是相同与并发的 CPU 的高速切换进行

在知乎上看到一个对并发和并行的一个回答:
你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。
你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。
你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。

线程:

线程,线程内部是共享一套数据的,但由于python的GIL锁限制,
  python 的每一个进程在同一时刻只能有一个线程在运行,而如果想同一个时刻运行多个线程的话,
  可以用多个进程来解决,但是进程消耗大,每个进程都有着不同的内存地址和数据,如果需要通信,
  那就要消耗相对与线程相比十几倍的资源去通信,不是必要,少用进程。
证明在 Python 中的线程都是并发的例子
import threading

def aa(a):
    while a > 0:
        print('a ========== ', a)
        a = a - 1

def bb(b):
    while b > 0:
        print('b = ', b)
        b = b - 1

t1 = threading.Thread(target=aa, args=(100,))
t2 = threading.Thread(target=bb, args=(100,))

t1.start()
t2.start()

输出结果:
(只贴出了一部分,可以看到 python 线程就是并发的,而不是并行的,一个运行,另一个就会停止)
b =  12
a ==========  3
a ==========  2
a ==========  1
b =  11
b =  10
b =  9
b =  8
b =  7
b =  6
b =  5
b =  4
b =  3
b =  2
b =  1
自定义 Thread ,要继承 threading.Thread,start 方法会调用 run 方法
import threading

class Mythread(threading.Thread):
    def __init__(self, a):
        threading.Thread.__init__(self)
        self.a = a
    def run(self):
        print('my run, a = ', self.a)

t = Mythread(100)
t.start()

输出结果:
 my run, a =  100
join 堵塞进程,就是在 t1.join 这句代码一直堵塞住,堵塞到 t1 执行完了,才执行 t2.join 这句。
import threading
import time

def aa():
    print(time.ctime()+'--a')
    time.sleep(3)
    print(time.ctime()+'--a')
def bb():
    print(time.ctime()+'-----b')
    time.sleep(6)
    print(time.ctime()+'-----b')

t1 = threading.Thread(target=aa)
t2 = threading.Thread(target=bb)

t1.start()
t2.start()

t1.join() # 在这里阻塞住,直到 线程 t1 执行完毕,
print('-------------------------------------------------------', time.ctime())
t2.join()

输出结果:
Wed Apr 25 20:15:18 2018--a
Wed Apr 25 20:15:18 2018-----b
Wed Apr 25 20:15:21 2018--a
------------------------------------------------------- Wed Apr 25 20:15:21 2018
Wed Apr 25 20:15:24 2018-----b
使用 join() 语句可以使得主线程等待子线程都执行完毕后才退出
import threading
import time

def aa():
    print(time.ctime()+'--a')
    time.sleep(3)
    print(time.ctime()+'--a')
def bb():
    print(time.ctime()+'-----b')
    time.sleep(6)
    print(time.ctime()+'-----b')

l = []
t1 = threading.Thread(target=aa)
l.append(t1)
t1.start()
t2 = threading.Thread(target=bb)
l.append(t2)
t2.start()
for i in l:
    i.join() #当线程 t1 执行结束,才执行下面的打印操作
    print(i)
print('-------------------------------------------------------', time.ctime())

输出结果:
Wed Apr 25 20:24:03 2018--a
Wed Apr 25 20:24:03 2018-----b
Wed Apr 25 20:24:06 2018--a
     # t1 执行完了,走下一句
Wed Apr 25 20:24:09 2018-----b

------------------------------------------------------- Wed Apr 25 20:24:09 2018
在默认情况下,子线程会执行完毕再退出,如果要设置子线程在主线程退出时退出,那么就要用到守护线程
守护线程,设置后的子线程跟随主线程一起退出,不管子线程是否执行完毕了,用 t.setDaemon(True),就是子线程守护主线程。
很重要的一点是,守护线程要设置在 t.start() 之前
import threading
import time

def aa():
    print(time.ctime()+'--a')
    time.sleep(3)
    print(time.ctime()+'--a')
def bb():
    print(time.ctime()+'-----b')
    time.sleep(6)
    print(time.ctime()+'-----b')

l = []
t1 = threading.Thread(target=aa)
l.append(t1)

t2 = threading.Thread(target=bb)
l.append(t2)

for i in l:
    i.setDaemon(True)  # 必须设置在 t.start() 之前
    i.start()
    print(i)
print('-------------------------------------------------------', time.ctime())

输出结果:
Wed Apr 25 20:36:36 2018--a

Wed Apr 25 20:36:36 2018-----b

------------------------------------------------------- Wed Apr 25 20:36:36 2018
#主线程一旦执行完,整个程序就立即退出了,子线程跟随主线程一齐退出

同步 & 异步

同步:当进程执行到一个IO(等待外部数据)的时候,-------在等待接收
异步                                       --------不等待,一直到数据接收成功,再回来处理

python 的 GIL ,也就是全局解释锁
因为有 GIL,所以,同一时刻,一个进程内,只有一个线程被 CPU 执行

任务可以分为,IO密集型和计算密集型,

对于 IO 密集型的任务,python的多线程是有意义的,time.sleep()是IO,爬虫也是。
对于计算密集型的任务,python的多线程就不推荐,可以采用多进程+协程
    (进程也是十分消耗资源,特别是进程之间的切换)

Python 同步锁
同步锁,就是加锁的模块不切换,变成串行的模式进行
不加同步锁的例子
import threading
import time

num = 100
def test():
    global num
    time.sleep(0.1)
    temp = num
    num = temp - 1

l = []

for i in range(100):
    t = threading.Thread(target=test)
    t.start()
    l.append(t)

for t in l:
    t.join()

print(num)


输出结果:
0  # 现在的 python 3 执行结果每次都是0
(------num 的输出值会随着每次执行都不相同,当第一个线程取到num的值时为100, 遇到time.sleep 
就切换到下一个线程,第二个线程取到的num的值还是100(因为第一个线程没有执行完赋值给num),
遇到time.sleep 还是切换到下一个线程,到第三个,第四个,········
直到第一个不再堵塞了,也就是第一个线程的time.sleep 执行完了,再赋值······-------
加了同步锁,也就是加了锁的代码在同一时刻只允许一个线程运行,就是程序在加锁的代码上是以串行执行的,所以下面这个程序的运行时间就变成了十秒多(0.1 * 100 = 10秒)
import threading
import time

num = 100
def test():
    global num
    A.acquire()
    time.sleep(0.1)
    temp = num
    num = temp - 1
    A.release()


A = threading.Lock()
l = []

for i in range(100):
    t = threading.Thread(target=test)
    t.start()
    l.append(t)

for t in l:
    t.join()

print(num)  // 0
同步锁会出现有死锁现象,就是线程A有A锁,想获得B锁,线程B有B锁,想获得A锁,彼此不后退,就成了死锁
import threading
import time

class Mythread(threading.Thread):
    def run(self):
        self.actionA()
        self.actionB()

    def actionA(self):
        A.acquire()
        print(self.name,'gotA',time.ctime())
        time.sleep(2)

        B.acquire()
        print(self.name,'gotB',time.ctime())
        time.sleep(1)

        B.release()
        A.release()

    def actionB(self):
        B.acquire()
        print(self.name,'gotB',time.ctime())
        time.sleep(2)

        A.acquire()
        print(self.name,'gotA',time.ctime())
        time.sleep(1)

        A.release()
        B.release()

if __name__ == '__main__':

    A = threading.Lock()
    B = threading.Lock()
    l = []
    for i in range(5):
        t = Mythread()
        t.start()
        l.append(t)

    for t in l:
        t.join()

    print('ending-------------------------')

输出结果:
Thread-1 gotA Thu Apr 26 08:30:31 2018
Thread-1 gotB Thu Apr 26 08:30:33 2018
Thread-1 gotB Thu Apr 26 08:30:34 2018
Thread-2 gotA Thu Apr 26 08:30:34 2018
(一直卡在这里,程序无法向后运行)
(也就是线程1把actionA函数执行完后,释放了A、B锁,A锁被释放后就被线程2抢占,执行actionA,
线程1要去执行actionB,获得了B锁,线程1、线程2等到各自从休眠中醒来时,都找不到各自需要的锁,
彼此又都不释放,就成了死锁。)
而解决的方法呢,运用递归锁,
递归锁,内部存在计数器,如果计数器为0,线程就可以拿,计数器每 acquire 一次加1,每 release 一次减1,不为0其他线程就不能拿

上面的程序改成下面的样子:

import threading
import time

class Mythread(threading.Thread):
    def run(self):
        self.actionA()
        self.actionB()

    def actionA(self):
        r_lock.acquire() #每 acquire 一次 RLock 内部计数器 count 加1
        print(self.name,'gotA',time.ctime())
        time.sleep(2)

        r_lock.acquire() # count = 2
        print(self.name,'gotB',time.ctime())
        time.sleep(1)

        r_lock.release() # count =1 每 release 一次 count 减1
        r_lock.release() # count = 0 RLock 的 count=0 就可以被其他线程调用了

    def actionB(self):
        r_lock.acquire()
        print(self.name,'gotB',time.ctime())
        time.sleep(2)

        r_lock.acquire()
        print(self.name,'gotA',time.ctime())
        time.sleep(1)

        r_lock.release()
        r_lock.release()

if __name__ == '__main__':

    r_lock = threading.RLock()
    l = []
    for i in range(5):
        t = Mythread()
        t.start()
        l.append(t)

    for t in l:
        t.join()

    print('ending-------------------------')

输出结果:
Thread-1 gotA Thu Apr 26 08:41:29 2018
Thread-1 gotB Thu Apr 26 08:41:31 2018
Thread-2 gotA Thu Apr 26 08:41:32 2018
Thread-2 gotB Thu Apr 26 08:41:34 2018
Thread-2 gotB Thu Apr 26 08:41:35 2018
Thread-2 gotA Thu Apr 26 08:41:38 2018
Thread-4 gotA Thu Apr 26 08:41:39 2018
Thread-4 gotB Thu Apr 26 08:41:41 2018
Thread-5 gotA Thu Apr 26 08:41:42 2018
Thread-5 gotB Thu Apr 26 08:41:44 2018
Thread-5 gotB Thu Apr 26 08:41:45 2018
Thread-5 gotA Thu Apr 26 08:41:47 2018
Thread-3 gotA Thu Apr 26 08:41:48 2018
Thread-3 gotB Thu Apr 26 08:41:50 2018
Thread-3 gotB Thu Apr 26 08:41:51 2018
Thread-3 gotA Thu Apr 26 08:41:53 2018
Thread-1 gotB Thu Apr 26 08:41:54 2018
Thread-1 gotA Thu Apr 26 08:41:56 2018
Thread-4 gotB Thu Apr 26 08:41:57 2018
Thread-4 gotA Thu Apr 26 08:41:59 2018
ending-------------------------
(整个程序就以串行的形式执行)
同步条件,event,三种属性,event.set():给出条件; event.clear():清除上次给的条件; event.wait() :不满足条件时一直堵塞
import threading
import time

def AA():
    time.sleep(1)
    print('aa',event.is_set()) #event.is_set() 方法检测是否设置了 set()
    event.set()
    time.sleep(5)
    print('aa, end')
    event.set()
def BB():
    event.wait() #没有 set 时一直堵塞
    print('bb')
    time.sleep(1)
    event.clear()

    event.wait()
    print('bb end')

if __name__ == '__main__':
    event = threading.Event()
    l = []
    for i in range(3):
        t = threading.Thread(target=BB) # 加入三个 BB 线程
        l.append(t)
    t = threading.Thread(target=AA)
    l.append(t)

    for i in l: # 线程A是加在list最后的,也是最后被运行的
        i.start()
    for i in l:
        i.join()

输出结果:
aa False #只有当AA给出同步条件后 event.wait() 才不再堵塞
bb
bb
bb
aa, end
bb end
bb end
bb end
信号量,也就是固定同时运行确定的线程数目,多的线程等到运行的线程结束才能运行
import threading
import time

def AA(x):
    if semaphore.acquire():
        print(x, time.ctime())
        time.sleep(3)
        print(x, 'end', time.ctime())
        semaphore.release()

if __name__ == '__main__':
    semaphore = threading.Semaphore(2)
    l = []
    for i in range(6):
        t = threading.Thread(target=AA, args=(i,))
        t.start()
        l.append(t)
    print('end--------')

输出结果:
0 Thu Apr 26 10:38:20 2018
1 Thu Apr 26 10:38:20 2018
end--------
0 end Thu Apr 26 10:38:23 2018
2 Thu Apr 26 10:38:23 2018
1 end Thu Apr 26 10:38:23 2018
3 Thu Apr 26 10:38:23 2018
2 end Thu Apr 26 10:38:26 2018
4 Thu Apr 26 10:38:26 2018
3 end Thu Apr 26 10:38:26 2018
5 Thu Apr 26 10:38:26 2018
4 end Thu Apr 26 10:38:29 2018
5 end Thu Apr 26 10:38:29 2018

队列,分为进程队列和线程队列,queue,涉及到数据安全,也就是线程间通信,最主要的是同步,先看一个不用队列的情况

import threading
import time

l = ['1','2','3','4','5']

def AA():
    a = l[-1]
    print(a)
    time.sleep(1)
    l.remove(a)

if __name__ == '__main__':
    li = []
    for i in range(2):
        t = threading.Thread(target=AA)
        t.start()
        li.append(t)
    for t in li:
        t.join()
    print('end--------')

输出结果直接报错了,可以看到有两个线程取到了一样的数字,当list中的5被删除后,
另一个线程的5就找不到了,自然而然就报错了
5
5
end--------
Exception in thread Thread-2:
Traceback (most recent call last):
  File "D:\anaconda\lib\threading.py", line 916, in _bootstrap_inner
    self.run()
  File "D:\anaconda\lib\threading.py", line 864, in run
    self._target(*self._args, **self._kwargs)
  File "C:/Users/libai/PycharmProjects/begin/a/process.py", line 10, in AA
    l.remove(a)
ValueError: list.remove(x): x not in list

队列的三种模式:

Queue() :先进先出,FIFO
LifoQueue() :先进后出,LIFO
PriorityQueue() :优先级模式,可以设定队列元素的优先级
import queue  # 属于线程队列
a = queue.LifoQueue
b = queue.PriorityQueue

q = queue.Queue() # 默认是先进先出模式,FIFO,可以加参数,
                  # 如queue.Queue(2) 默认只能放两个数,想要再放数据
#                 # 进去,就要等待了,会一直等待到有数据被取出,
                  # 自己的数据放进去了为止
q.put(12)
q.put('bbs')
q.put({'name':'li'})

while True:
    data = q.get() #get 不到数据也会一直等待,等到取到数据了为止
    print(data)
    print('------------------------------')

输出结果:
12
------------------------------
bbs
------------------------------
{'name': 'li'}
------------------------------
# ······没有执行完 一直堵塞

生产者、消费者模型,中间用队列作为中介处理,彼此解耦。
队列负责通信。

进程

进程跟线程调用的基本是同一套 api,使用方法跟线程几乎相同

import multiprocessing
import time

def a(title):
    print(title,time.ctime())
    time.sleep(2)
    print(title, 'end')

if __name__ == '__main__':
    process = multiprocessing.Process(target=a,args=('python',))
    process.start()
    process.join()
    print('ending -----------------',time.ctime())

输出结果:
python Thu Apr 26 16:43:57 2018
python end
ending ----------------- Thu Apr 26 16:43:59 2018

自定义的类继承 multiprocessing.Process,

import multiprocessing
import time

class MyProcess(multiprocessing.Process):
    def __init__(self,title):
        multiprocessing.Process.__init__(self)
        self.title = title

    def run(self):
        print('begin to run')
        time.sleep(2)
        print('hello', self.title, self.name, time.ctime())

if __name__ == '__main__':
    process = MyProcess('world')
    process.start()
    process.join()
    print('ending -----------------',time.ctime())

输出结果:
begin to run
hello world MyProcess-1 Thu Apr 26 16:53:50 2018
ending ----------------- Thu Apr 26 16:53:50 2018

关于进程的子进程和父进程

import multiprocessing
import os
import time

def info(title):
    print("title:", title)
    print('parent process:', os.getppid()) # 获得这个进程的父进程的 pid
    print('process id:', os.getpid()) # 获得这个进程的 pid

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

if __name__ == '__main__':
    info('main process line')
    time.sleep(1)
    print("------------------")
    p = multiprocessing.Process(target=info, args=('hello world',))
    p.start()
    p.join()

输出结果:
title: main process line
parent process: 6164 # 这个进程的父进程也就是 pycharm了(我用pycharm运行程序的)
process id: 6004 
------------------
title: hello world
parent process: 6004 # 子进程的父进程也就是 6004 了
process id: 12220 # 子进程

进程间通信,用进程队列,Queue, 主要的是数据同步,数据用复制的形式传到另一个进程

import multiprocessing
import time

def aa(q):
    print('begin')
    time.sleep(2)
    q.put(123)
    q.put('hello')
    print('son end, ',time.ctime())


if __name__ == '__main__':
    q = multiprocessing.Queue() # 进程队列
    process = multiprocessing.Process(target=aa,args=(q,))
    process.start()

    print(q.get())
    print(q.get())
    print('main end')

输出结果:
begin
son end,  Thu Apr 26 20:07:17 2018
123
hello
main end

管道通信

import multiprocessing
import time

def aa(conn):
    print('son id  = ',id(conn))
    time.sleep(1)
    conn.send('hello')
    print(conn.recv())

if __name__ == '__main__':
    p_conn, c_conn = multiprocessing.Pipe() # 双向管道
    process = multiprocessing.Process(target=aa,args=(c_conn,))

    process.start()
    print('main id  = ',id(p_conn))
    print(p_conn.recv())
    p_conn.send('libai')
    print('main end')

输出结果:
main id  =  1768407138600
son id  =  1942925877768
hello
main end
libai

Queue 和 Pipe 只是实现了数据交互, 并没实现数据共享,所谓的数据共享,即一个进程去更改另一个进程的数据。

import multiprocessing

def aa(l, i):
    l.append(i)

if __name__ == '__main__':
    with multiprocessing.Manager() as manager:
        l = manager.list('hello')
        process_list = []
        for i in range(5):
            process = multiprocessing.Process(target=aa, args=(l,i))
            process.start()
            process_list.append(process)
        for i in process_list:
            i.join()
        print(l)

输出结果:
['h', 'e', 'l', 'l', 'o', 0, 1, 2, 3, 4]
# 实现数据交互

进程同步,类似于线程内锁的概念

import multiprocessing
import time
def aa(lock, l,i):
    lock.acquire()
    time.sleep(1)
    print('hello')
    l.append(i)
    lock.release()

if __name__ == '__main__':
    lock = multiprocessing.Lock()
    l = multiprocessing.Manager().list()
    process_list = []
    for i in range(10):
        process = multiprocessing.Process(target=aa,args=(lock,l,i))
        process.start()
        process_list.append(process)
    for p in process_list:
        p.join()
    print(l)

输出结果:
hello
hello
hello
hello
hello
hello
hello
hello
hello
hello
[0, 2, 1, 3, 4, 5, 6, 7, 8, 9]

进程池, Pool

import multiprocessing
import time

def aa(i):
    time.sleep(5)
    print('hello', i)

if __name__ == '__main__':
    pool = multiprocessing.Pool(3)  # 不填参数就会以 CPU 核数作为 max
    for i in range(5):
        print(" 进程放入任务 " + str(i))
        # pool.apply(func=aa, args=(i,)) # 同步接口,就是串行执行
        pool.apply_async(func=aa, args=(i,))  # 异步接口,并发或并行

    pool.close()  # 关闭进程池,关闭后 pool 不再接收新的请求
    pool.join()  # close 和 join 顺序是固定的,等待子进程都执行完毕
    print("program end ----")

输出结果:
 进程放入任务 0
 进程放入任务 1
 进程放入任务 2
 进程放入任务 3
 进程放入任务 4
hello 0
hello 1
hello 2
hello 3
hello 4
program end ----

回调函数,callback() ,子进程中有时候并不能并行的执行某些操作,例如操作文件打印日志和修改数据,可以交给 callback() ,让主进程串行执行

import multiprocessing
import time
import os
def aa(i):
    time.sleep(1)
    print('hello',i, os.getpid())

def Bar(arg):
    print('bar',os.getpid())

if __name__ == '__main__':
    pool = multiprocessing.Pool(5)
    print('main ',os.getpid())
    for i in range(10):
        # 回调函数: 就是某个动作或者函数执行成功后再去执行的函数
        # 回调函数是在主进程中调用的,而不是子进程
        pool.apply_async(func=aa, args=(i,),callback=Bar)

    pool.close()
    pool.join()

输出结果:
main  11048
hello 0 1228
bar 11048
hello 1 9348
bar 11048
hello 2 10280
bar 11048
hello 3 8768
bar 11048
hello 4 8544
bar 11048
hello 5 1228
bar 11048
hello 6 9348
bar 11048
hello 7 10280
bar 11048
hello 8 8768
bar 11048
hello 9 8544
bar 11048
# 可以看出,回调函数的 bar 是在主进程中运行的

在回调函数中,可以获取到返回值

import multiprocessing
import time
def aa(i):
    time.sleep(1)
    return i # 返回的值被 callback 函数 Bar 接收到,

def Bar(arg):
    print('bar get i = ',arg)

if __name__ == '__main__':
    pool = multiprocessing.Pool(5)
    for i in range(10):
        pool.apply_async(func=aa, args=(i,),callback=Bar)

    pool.close()
    pool.join()

输出结果:
bar get i =  0
bar get i =  1
bar get i =  2
bar get i =  3
bar get i =  4
bar get i =  5
bar get i =  6
bar get i =  7
bar get i =  8
bar get i =  9
for temp in xxx_obj:
    pass
1. 判断 xxx_obj 是否是可以迭代
2. 在第一步成立的前提下,调用 iter 函数,得到 xxx_obj 对象的 __iter__ 方法的返回值
3. __iter__ 的方法的返回值是一个迭代器

生成器,yield,每 next 一次,遇到 yield 就返回

def test():
    print('one')
    yield 1
    print('two')
    yield


t = test() # 返回一个生成器对象
print(t)
res1 = t.__next__() # 执行到yield 1 停止
print(res1)
print('------------------------------------------------------')
res2 = next(t) #从上次执行到的 yield 1 后面继续执行, 但是并没有设定返回值
print(res2)

输出结果:

one
1
------------------------------------------------------
two
None

生成器,yield 传值

def test():
    print('one')
    s = yield 1
    print(s)
    print('two')
    yield

t = test()
res1 = t.__next__()
print(res1)
print('------------------------------------------------------')
t.send(150) # send 相当于一个 next

输出结果:
one
1
------------------------------------------------------
150
two

自定义生成器和迭代器类

一个实现了iter方法和next方法的对象,就是迭代器

from collections import Iterable
from collections import Iterator

class Classmate():  // 生成器
    def __init__(self):
        self.names = list()

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

    def __iter__(self):
        "如果想要一个对象成为一个可以迭代的对象,既可以使用 for,那么必须实现__iter__方法"
        return ClassIterator(self)

class ClassIterator():  // 迭代器
    i = -1

    def __init__(self, classmate):
        self.names = classmate.names

    def __iter__(self):
        pass

    def __next__(self):
        self.i = self.i + 1
        if self.i == self.names.__len__():
            # 抛出这个异常会使得迭代器停止
            raise StopIteration
        return self.names[self.i]

c = Classmate()

c.add("libai")
c.add("hello")
c.add("talent")

print(c.names)

# for...in...循环的本质
# for item in Iterable 循环的本质就是先通过iter()函数获取可迭代对象Iterable的迭代器,
# 然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值给item,
# 当遇到StopIteration的异常后循环结束。
for name in c:
    print(name)

print("是否可迭代:" + str(isinstance(c, Iterable)))
print("是否是迭代器:" + str(isinstance(iter(c), Iterator)))

print(next(iter(c)))

打印结果:
['libai', 'hello', 'talent']
libai
hello
talent
是否可迭代:True
是否是迭代器:True
libai

上面代码修改成只有一个迭代器,也可以完成迭代

from collections import Iterable
from collections import Iterator


class Classmate():
    i = -1

    def __init__(self):
        self.names = list()

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

    def __iter__(self):
        "如果想要一个对象成为一个可以迭代的对象,既可以使用 for,那么必须实现__iter__方法"
        return self

    def __next__(self):
        self.i = self.i + 1
        if self.i == self.names.__len__():
            # 抛出这个异常会使得迭代器停止
            raise StopIteration
        return self.names[self.i]

c = Classmate()

c.add("libai")
c.add("hello")
c.add("talent")

print(c.names)

for name in c:
    print(name)

print("是否可迭代:" + str(isinstance(c, Iterable)))
print("是否是迭代器:" + str(isinstance(c, Iterator)))

打印结果:
['libai', 'hello', 'talent']
libai
hello
talent
是否可迭代:True
是否是迭代器:True

用迭代器生成斐波那契数列,而不必生成一个list来存储数据,减少占用的空间

from collections import Iterator
from collections import Iterable

class fiboacci():
    i = 1
    a = 0
    b = 1
    def __init__(self, num):
        self.num = num

    def __iter__(self):
        return self

    def __next__(self):
        if self.i > self.num:
            raise StopIteration
        else:
            self.a, self.b = self.b, self.a+self.b
            self.i += 1
            return self.b - self.a

fibo = fiboacci(10)
print(isinstance(fibo, Iterable))
print(isinstance(fibo, Iterator))

for i in fibo:
    print(i, end=", ")

打印结果:
True
True
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 

Python3 中的 range() 也是一个迭代器,占用极小的内存空间

print(type(range(100)))  // 
并不是只有for循环能接收可迭代对象
除了for循环能接收可迭代对象,list、tuple等也能接收。

print(list(fiboacci(10)))
print(tuple(fiboacci(10)))
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
(0, 1, 1, 2, 3, 5, 8, 13, 21, 34)

生成器,包含有 yield

def getnum(num): # 生成器是一种特殊的迭代器

    a = 0
    b = 1
    while a < num:
        yield a # 如果一个函数中有yield语句,那么这个就不再是函数,而是一个生成器
        a, b = b, a+b

# 如果在调用函数时,发现这个函数中有yield,不是调用函数,而是创建一个生成器对象
for i in getnum(200):
    print(i, end=", ")

print()
print(next(getnum(10)))

打印结果:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 
0

用 next() 、send() 来调用生成器

def getnum(num): # 生成器是一种特殊的迭代器

    a = 0
    b = 1
    while a < num:
        yield a
        a, b = b, a+b
    return "end"

obj = getnum(100)
while True:
    try:
        ret = next(obj)
        print(ret, end=", ")
    except StopIteration as ret:  # 可以获取到生成器函数的返回值
        print("\n" + ret.value)
        break

打印结果:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 
end

用 obj. send("hello") 也可以唤醒生成器,在 ret = yeild a 接收返回值

协程,协作式,非抢占式的程序,关键在 yield,主要是什么时候切换,解决的也是 IO 操作

协程本质上也就是一个线程
优势:
1. 没有切换的消耗
2. 没有锁的概念
3. 要使用多核,可以和多进程配合

协程例子

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()

输出结果:
---- 1 -----
---- 2 -----
---- 1 -----
---- 2 -----
---- 1 -----
...省略...

greenlet 模块,为了更好使用协程来完成多任务,greenlet模块对其封装,从而使得切换任务变的更加简单, 但不是很实用

import greenlet

def test1():
    print('12')
    gr2.switch()
    print('56')
    gr2.switch()

def test2():
    print('34')
    gr1.switch()
    print('78')

gr1 = greenlet.greenlet(test1)
gr2 = greenlet.greenlet(test2)

gr1.switch()

输出结果:
12
34
56
78

gevent 模块, 遇到 IO 就切换,但对于 time.sleep() ,切换不了

import gevent
import time

def f_1(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        time.sleep(0.5)
        # gevent.sleep(0.5)

def f_2(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        time.sleep(0.5)
        # gevent.sleep(0.5)

def f_3(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        time.sleep(0.5)
        # gevent.sleep(0.5)

print("1")
g1 = gevent.spawn(f_1, 5)  # 任务被添加到列表中,gevent可以自动开始执行
print("2")
g2 = gevent.spawn(f_2, 5)
print("3")
g3 = gevent.spawn(f_3, 5)

print("4")
g1.join()  # 这句在堵塞,直到 g1 任务执行完毕
print("5")
g2.join()
print("6")
g3.join()
print("7")

打印结果:
1
2
3
4
 0
 1
 2
 3
 4
 0
 1
 2
 3
 4
 0
 1
 2
 3
 4
5
6
7
改成使用 gevent.sleep() 方法,即可切换(其他都相同)
import gevent
import time

def f_1(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        # time.sleep(0.5)
        gevent.sleep(0.5)

def f_2(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        # time.sleep(0.5)
        gevent.sleep(0.5)

def f_3(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        # time.sleep(0.5)
        gevent.sleep(0.5)

print("1")
g1 = gevent.spawn(f_1, 5)  # 任务被添加到列表中,gevent可以自动开始执行
print("2")
g2 = gevent.spawn(f_2, 5)
print("3")
g3 = gevent.spawn(f_3, 5)

print("4")
g1.join()  # 这句在堵塞,直到 g1 任务执行完毕
print("5")
g2.join()
print("6")
g3.join()
print("7")

打印结果:
1
2
3
4
 0
 0
 0
 1
 1
 1
 2
 2
 2
 3
 3
 3
 4
 4
 4
5
6
7
还有另一种打补丁的方式,不用修改 time.sleep() 语句(内部自动替换)
import gevent
from gevent import monkey
import time

monkey.patch_all()

def f_1(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        time.sleep(0.5)
        # gevent.sleep(0.5)

def f_2(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        time.sleep(0.5)
        # gevent.sleep(0.5)

def f_3(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        time.sleep(0.5)
        # gevent.sleep(0.5)

print("1")
g1 = gevent.spawn(f_1, 5)  # 任务被添加到列表中,gevent可以自动开始执行
print("2")
g2 = gevent.spawn(f_2, 5)
print("3")
g3 = gevent.spawn(f_3, 5)

print("4")
g1.join()  # 这句在堵塞,直到 g1 任务执行完毕
print("5")
g2.join()
print("6")
g3.join()
print("7")

打印结果:
1
2
3
4
 0
 0
 0
 1
 1
 1
 2
 2
 2
 3
 3
 3
 4
 4
 4
5
6
7
简写方式
import gevent
import time
from gevent import monkey
import random

# 有耗时操作时需要
monkey.patch_all()  # 将程序中用到的耗时操作的代码,换为gevent中自己实现的模块

def work(current_name):
    for i in range(2):
        print(current_name, i)
        time.sleep(random.random())

gevent.joinall([
        gevent.spawn(work, "work1"),
        gevent.spawn(work, "work2")
    ])

print("end")

打印结果:
work1 0
work2 0
work1 1
work2 1
end
使用 gevent 调用请求IO的例子
import time
import requests
import gevent

start = time.time()
url_list = ['https://www.baidu.com/',
            'http://www.sina.com.cn/',
            'https://www.jianshu.com/'
            ]
def req(url):
    requests.get(url)
    print('get url',url)

gevent.joinall([
    gevent.spawn(req, url_list[0]),
    gevent.spawn(req, url_list[1]),
    gevent.spawn(req, url_list[2]),
])

print('cost time : ', time.time()-start)

输出结果:
get url https://www.baidu.com/
get url http://www.sina.com.cn/
get url https://www.jianshu.com/
cost time :  0.6288654804229736
# 和串行执行的结果消耗时间相差不多。

进程、线程、协程对比

请仔细理解如下的通俗描述
有一个老板想要开个工厂进行生产某件商品(例如剪子)
他需要花一些财力物力制作一条生产线,这个生产线上有很多的器件以及材料这些所有的 为了能够生产剪子而准备的资源称之为:进程
只有生产线是不能够进行生产的,所以老板的找个工人来进行生产,这个工人能够利用这些材料最终一步步的将剪子做出来,这个来做事情的工人称之为:线程
这个老板为了提高生产率,想到3种办法:
1. 在这条生产线上多招些工人,一起来做剪子,这样效率是成倍増长,即单进程 多线程方式
2. 老板发现这条生产线上的工人不是越多越好,因为一条生产线的资源以及材料毕竟有限,
    所以老板又花了些财力物力购置了另外一条生产线,然后再招些工人这样效率又再一步提高了,即多进程 多线程方式
3. 老板发现,现在已经有了很多条生产线,并且每条生产线上已经有很多工人了(即程序是多进程的,每个进程中又有多个线程),为了再次提高效率,老板想了个损招,
    规定:如果某个员工在上班时临时没事或者再等待某些条件(比如等待另一个工人生产完谋道工序 之后他才能再次工作),那么这个员工就利用这个时间去做其它的事情,
    那么也就是说:如果一个线程等待某些条件,可以充分利用这个时间去做其它事情,其实这就是:协程方式

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

计算密集型:进程
IO密集型:线程、协程

并发编程:https://www.cnblogs.com/yuanchenqi/articles/6248025.html

你可能感兴趣的:(线程、进程、迭代器、生成器、yield协程)