进程

程序:例如xxx.py这是程序,是一个静态的

进程:一个程序运行起来后,代码+用到的资源称之为进程,它是操作系统分配资源的基本单元。

不仅可以通过线程完成多任务,进程也是可以的

进程和线程的区别?

1.进程:通俗理解一个运行起来的程序或者软件就叫做进程,
每启动一个进程,操作系统都需要给对应的进程分配运行资源,
运行资源是让线程执行代码的时候使用,所以进程是操作系统分配资源的基本单位,默认一个进程只有一个线程,这个线程是主线程
进程只提供资源,真正干活的是线程

2.0线程和进程的区别
2.1线程之间共享全局变量
2.2进程之间不共享全局变量
2.3进程:每开辟一个进程都需要向操作系统分配资源,而线程可以共享进程中的资源
2.4多进程开发比单进程开发程序的稳定性要强,因为多进程开发,某个进程死了,不会影响启动进程的运行

注意点 多进程开发需要更多资源,而多线程开发可以共享进程中的资源

Python中使用过的进程模块?

multiprocessing

  • multiprocessing是一个使用类似于线程模块的API支持产生进程的包。
  • 多处理包提供本地和远程并发,通过使用子进程而不是线程有效地侧向执行全局解释器锁。
  • 因此,多处理模块允许程序员充分利用给定机器上的多个处理器。 它可以在Unix和Windows上运行。
  • 进程池抓取页面
# -*- coding: utf-8 -*-
import requests
from multiprocessing import Pool

def fetch_request(url):
    result = requests.get(url)
    print(result.text)

def call(arg):
    print('-->exec done:',"测试进程池执行后回调功能")

url_list = [
    'https://www.baidu.com',
    'https://www.google.com/',         #google页面会卡住,知道页面超时后这个进程才结束
    'http://dig.chouti.com/',          #chouti页面内容会直接返回,不会等待Google页面的返回
]

if __name__ == '__main__':
    pool = Pool(10)        # 创建线程池
    for url in url_list:
        #用法1 callback作用是指定只有当Foo运行结束后就执行callback调用的函数,父进程调用的callback函数
        pool.apply_async(func=fetch_request, args=(url,),callback=call)
    print('end')
    pool.close()    #关闭pool
    pool.join()     #进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。

concurrent.futures

  • 1、简介 参考官网
    • 1、Python标准库为我们提供了threading和multiprocessing模块编写相应的多线程/多进程代码
    • 2、但是当项目达到一定的规模,频繁创建/销毁进程或者线程是非常消耗资源的,这个时候我们就要编写自己的线程池/进程池,以空间换时间。
    • 3、但从Python3.2开始,标准库为我们提供了concurrent.futures模块,它提供了ThreadPoolExecutor和ProcessPoolExecutor两个类,
    • 4、实现了对threading和multiprocessing的进一步抽象,对编写线程池/进程池提供了直接的支持。
  • 2、Executor和Future
    • 1. Executor
    • concurrent.futures模块的基础是Exectuor,Executor是一个抽象类,它不能被直接使用。
    • 但是它提供的两个子类ThreadPoolExecutor和ProcessPoolExecutor却是非常有用
    • 我们可以将相应的tasks直接放入线程池/进程池,不需要维护Queue来操心死锁的问题,线程池/进程池会自动帮我们调度。
    • 2. Future
    • Future你可以把它理解为一个在未来完成的操作,这是异步编程的基础,
    • 传统编程模式下比如我们操作queue.get的时候,在等待返回结果之前会产生阻塞,cpu不能让出来做其他事情
    • 而Future的引入帮助我们在等待的这段时间可以完成其他的操作。
  • 3、concurrent.futures.ProcessPoolExecutor 抓取网页
import requests
from concurrent.futures import ProcessPoolExecutor

def fetch_request(url):
    result = requests.get(url)
    print(result.text)

url_list = [
    'https://www.baidu.com',
    'https://www.google.com/',         #google页面会卡住,知道页面超时后这个进程才结束
    'http://dig.chouti.com/',          #chouti页面内容会直接返回,不会等待Google页面的返回
]

if __name__ == '__main__':
    pool = ProcessPoolExecutor(10)        # 创建线程池
    for url in url_list:
        pool.submit(fetch_request,url)    # 去线程池中获取一个进程,进程去执行fetch_request方法
    pool.shutdown(wait = True)
    # shutdown相当于一个开关,它会读取程序中所设定的进程总数,直至每开启一个进程,它读取设定的总数就会减一,直至为0时便会打印主线程

Process语法结构

​ Process([group [, target [, name [, args [, kwargs]]]]])
​ target:如果传递了函数的引用,可以任务这个子进程就执行这里的代码 tardet=
​ args:给target指定的函数传递的参数,以元组的方式传递 (n,)
​ kwargs:给target指定的函数传递命名参数 {“n”,”m”}
​ name:给进程设定一个名字,可以不设定 name =
​ group:指定进程组,大多数情况下用不到

Process创建的实例对象的常用方法:

​ start():启动子进程实例(创建子进程) 进程变量名.start()
​ is_alive():判断主进程或子进程是否还在活着 print(进程变量名.is_alive)
​ join([timeout]):是否等待子进程执行结束,或等待多少秒 进程变量名.join()
​ terminate():不管任务是否完成,立即终止子进程 进程变量名.terminate()

守护主进程
# copy_work_process.daemon = True   

Process创建的实例对象的常用属性:
name:当前进程的别名,默认为Process-N,N为从1开始递增的整数
pid:当前进程的pid(进程号) 父类的ppid

僵尸进程

  • 1)僵尸进程定义
      1. 僵尸进程产生的原因就是父进程产生子进程后,子进程先于父进程退出
      1. 但是父进程由于种种原因,并没有处理子进程发送的退出信号,那么这个子进程就会成为僵尸进程。
  • 2)用python写一个僵尸进程
#!/usr/bin/env python
#coding=utf8
 
import os, sys, time
#产生子进程
pid = os.fork()
 
if pid == 0:
    #子进程退出
    sys.exit(0)
#父进程休息30秒
time.sleep(30)
# 先产生一个子进程,子进程退出,父进程休息30秒,那就会产生一个僵尸进程
  • ps -ef| grep defunct 在linux下查看僵尸进程

    • ```python
      [root@linux-node4 ~]# ps -ef| grep defunct
      root 110401 96083 0 19:11 pts/2 00:00:00 python defunct.py
      root 110402 110401 0 19:11 pts/2 00:00:00 [python]
      root 110406 96105 0 19:11 pts/3 00:00:00 grep –color=auto defunct
      
      ## 进程的格式
      
      ```python
      import multiprocessing
      
      
      def func(name, age, sex):
          pass
      
      
      if __name__ == '__main__':
          # 创建线程对象      (target = 执行目标函数名)
          say_thread = multiprocessing.Process(target=func, kwargs={'name': "老王", 'sex': '男', 'age': "18", })
          # 启动
          say_thread.start()
      
      # 进程之间不共享全局变量
      
      import multiprocessing
      
      a = [11, 22]
      
      
      def fun():
          a.append(33)
          print(a)
      
      
      def fun1():
          a.append(44)
          print(a)
      
      
      if __name__ == '__main__':
          mult_fun = multiprocessing.Process(target=fun)
          mult_fun1 = multiprocessing.Process(target=fun1)
      
          mult_fun.start()
          mult_fun1.start()
      

判断进程是否存活

# name 给进程设定一个名字,可以不设定
# is_alive():判断主进程或子进程是否还在活着
# 有这俩种方法的demo

# import multiprocessing,time
#
# def copy_work():
#     for i in range(5):
#         print("正在复制中---",i+1)
#         time.sleep(0.5)
#
# def copy_work2():
#     for i in range(5):
#         print("正在复制中---",i+1)
#         time.sleep(0.5)
#
#
# if __name__ == '__main__':
#     #创建子进程
#     copy_work_process = multiprocessing.Process(target=copy_work,name="laowang")
#     copy_work_process2 = multiprocessing.Process(target=copy_work,name="laowang")
#     print(copy_work_process)
#     print(copy_work_process2)
#     #启动进程
#     copy_work_process.start()
#     copy_work_process2.start()
#     # time.sleep(5)   #False
#     time.sleep(1)   #True
#     print(    copy_work_process.is_alive())
#     print(    copy_work_process2.is_alive())

销毁子进程查看进程编号

"""

翔翔的demo

"""

# 主线程跟子线程同时结束的demo

# import multiprocessing,time
#
# def copy_work():
#     for i in range(5):
#         print("正在复制中---",i+1)
#         time.sleep(0.5)
#
# def copy_work2():
#     for i in range(5):
#         print("正在复制中---",i+1)
#         time.sleep(0.5)
#
#
# if __name__ == '__main__':
#     print("主线程开始")
#     #创建子进程
#     copy_work_process = multiprocessing.Process(target=copy_work,name="laowang")
#     copy_work_process2 = multiprocessing.Process(target=copy_work,name="laowang")
#     # 方法一  守护主进程
#     # copy_work_process.daemon = True
#     # copy_work_process2.daemon = True
#
#     #启动进程
#     copy_work_process.start()
#     copy_work_process2.start()
#     # copy_work_process.join()
#     time.sleep(1)
#     print("主线程结束")
#     # 方法二  销毁子进程
#     copy_work_process.terminate()
#     copy_work_process2.terminate()


# "子进程编号:",os.getpid()
# "主进程编号:",os.getppid()


import multiprocessing, time, os


def func(name, age, sex):
    print("子进程编号:", os.getpid())
    print("主进程编号:", os.getppid())
    time.sleep(1)


if __name__ == '__main__':
    print("启动", os.getpid())
    # 创建线程对象      (target = 执行目标函数名)
    say_thread = multiprocessing.Process(target=func, kwargs={'name': "老王", 'sex': '男', 'age': "18", })
    # 启动
    say_thread.start()
    print("结束")

进程间的通信

"""

翔翔的demo

"""

"""
Process之间有时需要通信,可以使用multiprocessing模块的Queue实现多进程之间的数据传递,Queue本身是一个消息队列程序

初始化Queue()对象时(例如:q=Queue(5)),若括号中没有指定最大可接收的消息数量,那么就代表可接受的消息数量没有上限(直到内存的尽头);

Queue.qsize():返回当前队列包含的消息数量;
Queue.empty():如果队列为空,返回True,反之False ;
Queue.full():如果队列满了,返回True,反之False;
Queue.get([block[, timeout]]):获取队列中的一条消息,然后将其从列队中移除,block默认值为True;
Queue.put(item,[block[, timeout]]):将item消息写入队列,block默认值为True
"""

import multiprocessing, time


def fun1(queue):
    for i in range(10):
        if queue.full():
            print("队列满了")
            break
        else:
            queue.put(i)
            print("写入的数据为:", i)
            time.sleep(1)


def fun2(queue):
    while True:
        if queue.empty():
            print("队列为空")
            break
        else:
            ss = queue.get()
            print(ss)


if __name__ == '__main__':
    queue = multiprocessing.Queue(5)
    fun1_process = multiprocessing.Process(target=fun1, args=(queue,))
    fun2_process = multiprocessing.Process(target=fun2, args=(queue,))

    fun1_process.start()
    fun1_process.join()
    fun2_process.start()
    fun2_process.join()

进程池

进程池的作用:快速批量创建进程

"""
翔翔的demo

"""

from multiprocessing import *
import time, os

i = 0


def copy_work():
    global i
    i += 1
    print("正在复制中---%d" % i)
    time.sleep(3)


if __name__ == '__main__':
    # 创建进程池
    # 指定进程池数量表示进程池中最多一次性创建的数列
    pool = Pool(3)

    for i in range(10):
        # 让进程池执行复制任务
        # 使用同步的方式去执行任务,进程池中的需要等待其它进程执行完成后才能执行置地指定任务
        # pool.apply(copy_work)
        # 异步执行进程池中的进程一起执行,不会等待其他进程的执行
        pool.apply_async(copy_work)
    # 提示--主进程不会等待进程池把执行任务完成以后程序在退出
    # 提示--进程池不在接受qi其它需要执行的任务
    pool.close()
    # 等待进程池把任务执行完成以后程序在退出
    # 同时具有启动进程的作用
    pool.join()

线程

什么是线程

  • 1)线程是操作系统调度的最小单位
  • 2)线程是进程正真的执行者,是一些指令的集合(进程资源的拥有者)
  • 3)同一个进程下的多个线程共享内存空间,数据直接访问(数据共享)
  • 4)为了保证数据安全,必须使用线程锁

说明:下面利用for循环同时启动50个线程并行执行,执行时间是3秒而不是所有线程执行时间的总和

import threading
import time

def sayhi(num): #定义每个线程要运行的函数
    print("running on number:%s" %num)
    time.sleep(3)
for i in range(50):
    t = threading.Thread(target=sayhi,args=('t-%s'%i,))
    t.start()

GIL锁和线程锁

  • GIL全局解释器锁
    • 在python全局解释器下,保证同一时间只有一个线程运行
    • 防止多个线程都修改数据
  • 线程锁(互斥锁)
    • GIL锁只能保证同一时间只能有一个线程对某个资源操作,但当上一个线程还未执行完毕时可能就会释放GIL,其他线程就可以操作了
    • 线程锁本质把线程中的数据加了一把互斥锁
      • 加上线程锁之后所有其他线程,读都不能读这个数据
    • 有了GIL全局解释器锁为什么还需要线程锁
      • 因为cpu是分时使用的
  • 在有GIL的情况下执行 count = count + 1 会出错原因解析,用线程锁解决方法
# 1)第一步:count = 0   count初始值为0
# 2)第二步:线程1要执行对count加1的操作首先申请GIL全局解释器锁
# 3)第三步:调用操作系统原生线程在操作系统中执行
# 4)第四步:count加1还未执行完毕,时间到了被要求释放GIL
# 5)第五步:线程1释放了GIL后线程2此时也要对count进行操作,此时线程1还未执行完,所以count还是0
# 6)第六步:线程2此时拿到count = 0后也要对count进行加1操作,假如线程2执行很快,一次就完成了
#    count加1的操作,那么count此时就从0变成了1
# 7)第七步:线程2执行完加1后就赋值count=1并释放GIL
# 8)第八步:线程2执行完后cpu又交给了线程1,线程1根据上下文继续执行count加1操作,先拿到GIL
#    锁,完成加1操作,由于线程1先拿到的数据count=0,执行完加1后结果还是1
# 9)第九步:线程1将count=1在次赋值给count并释放GIL锁,此时连个线程都对数据加1,但是值最终是1
  • ```
    死锁定义
    
      - 两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去
    
    ## 创建线程
    
    ```python
    # 单线程
    # import threading,time
    #
    # def func():
    #     for i in range(5):
    #         print("hello python")
    #         time.sleep(1)
    #
    # if __name__ == '__main__':
    # 创建线程对象
    #     say_thread = threading.Thread(target=func)
    # 启动
    #     say_thread.start()
    
    
    # 有参数的  args= ()    kwargs = {}
    import threading, time
    
    
    def func(name, age, sex):
        print(name, age, sex)
    
    
    if __name__ == '__main__':
        # 创建线程对象      (target = 执行目标函数名)
        say_thread = threading.Thread(target=func, kwargs={'name': "老王", 'sex': '男', 'age': "18", })
        # 启动
        say_thread.start()
    

线程的性质 无序性

# .join()       #线程等待 执行完一个在执行下一个

import threading, time


def func(num):
    for i in range(num):
        print("hello python")
        time.sleep(1)


def func1(num1):
    for i in range(num1):
        print("hello world")
        time.sleep(1)


if __name__ == '__main__':
    print(threading.current_thread())  # <_MainThread(MainThread, started 11156)>
    # 在demo未创建程序之前,默认存在一个主线程,这个主线程的作用为:执行程序下方的代码,当代码执行完毕后,主线程默认结束
    # 创建子线程对象
    print("线程开始执行")
    say_thread = threading.Thread(target=func, args=(5,))
    say_thread1 = threading.Thread(target=func1, args=(6,))
    # 启动
    say_thread.start()
    say_thread.join()  # 线程等待 执行完一个在执行下一个
    say_thread1.start()
    say_thread1.join()  # 线程等待 执行完一个在执行下一个

    print("线程结束执行")

多线程共享全局变量

# 多线程在共享全局变量中
# 优点:重复代码的复用率(重复利用的简写)
# 缺点:由于多线程之间出现了资源的恶意竞争问题,进而的会导致计算结果的错误和混乱

# 解决缺点的方法
# 1.0 join()


# 共享全局变量
# import threading
# alist = [11,22]
#
# def func1():
#     alist.append(33)
#     print(alist)
#
# def func2():
#     alist.append(44)
#     print(alist)
#
# if __name__ == '__main__':
#     #创建子线程
#     func1_thread = threading.Thread(target=func1)
#     func2_thread = threading.Thread(target=func2)
#     #启动
#     func1_thread.start()
#     func2_thread.start()


import threading

a = 0


def func1():
    global a
    for i in range(1000000):
        a += 1
    print(a)


def func2():
    global a
    for i in range(1000000):
        a += 1
    print(a)


if __name__ == '__main__':
    # 创建子线程
    func1_thread = threading.Thread(target=func1)
    func2_thread = threading.Thread(target=func2)
    # 启动
    func1_thread.start()
    func1_thread.join()
    func2_thread.start()

join()和setDaemon()

join()

  • 实现所有线程都执行结束后再执行主线程
import threading
import time
start_time = time.time()

def sayhi(num): #定义每个线程要运行的函数
    print("running on number:%s" %num)
    time.sleep(3)
for i in range(50):
    t = threading.Thread(target=sayhi,args=('t-%s'%i,))
    t.setDaemon(True)  #把当前线程变成守护线程,必须在t.start()前设置
    t.start()          #启动一个线程,程序不会阻塞
print('cost time:',time.time() - start_time)

setDaemon()

  • 守护线程,主线程退出时,需要子线程随主线程退出
import threading
import time
start_time = time.time()

def sayhi(num): #定义每个线程要运行的函数
    print("running on number:%s" %num)
    time.sleep(3)
for i in range(50):
    t = threading.Thread(target=sayhi,args=('t-%s'%i,))
    t.setDaemon(True)  #把当前线程变成守护线程,必须在t.start()前设置
    t.start()          #启动一个线程,程序不会阻塞
print('cost time:',time.time() - start_time)

Python中使用过的线程模块?

threading

  • Python提供了几个用于多线程编程的模块,包括thread、threading和Queue等。
  • thread和threading模块允许程序员创建和管理线程。
import threading
import time

def sayhi(num): #定义每个线程要运行的函数
    print("running on number:%s" %num)
    time.sleep(3)
    
for i in range(50):
    t = threading.Thread(target=sayhi,args=('t-%s'%i,))
    t.start()

concurrent.futures

  • 1、简介 参考官网
    • 1、Python标准库为我们提供了threading和multiprocessing模块编写相应的多线程/多进程代码
    • 2、但是当项目达到一定的规模,频繁创建/销毁进程或者线程是非常消耗资源的,这个时候我们就要编写自己的线程池/进程池,以空间换时间。
    • 3、但从Python3.2开始,标准库为我们提供了concurrent.futures模块,它提供了ThreadPoolExecutor和ProcessPoolExecutor两个类,
    • 4、实现了对threading和multiprocessing的进一步抽象,对编写线程池/进程池提供了直接的支持。
  • 2、Executor和Future
    • 1. Executor
      • concurrent.futures模块的基础是Exectuor,Executor是一个抽象类,它不能被直接使用。
      • 但是它提供的两个子类ThreadPoolExecutor和ProcessPoolExecutor却是非常有用
      • 我们可以将相应的tasks直接放入线程池/进程池,不需要维护Queue来操心死锁的问题,线程池/进程池会自动帮我们调度。
    • 2. Future
      • Future你可以把它理解为一个在未来完成的操作,这是异步编程的基础,
      • 传统编程模式下比如我们操作queue.get的时候,在等待返回结果之前会产生阻塞,cpu不能让出来做其他事情
      • 而Future的引入帮助我们在等待的这段时间可以完成其他的操作。
  • concurrent.futures.ThreadPoolExecutor 抓取网页
import requests
from concurrent.futures import ThreadPoolExecutor

def fetch_request(url):
    result = requests.get(url)
    print(result.text)

url_list = [
    'https://www.baidu.com',
    'https://www.google.com/',         #google页面会卡住,知道页面超时后这个进程才结束
    'http://dig.chouti.com/',          #chouti页面内容会直接返回,不会等待Google页面的返回
]

pool = ThreadPoolExecutor(10)            # 创建一个线程池,最多开10个线程
for url in url_list:
    pool.submit(fetch_request,url)       # 去线程池中获取一个线程,线程去执行fetch_request方法

pool.shutdown(True)                      # 主线程自己关闭,让子线程自己拿任

锁 互斥锁 排插锁 悲观锁

# 全局解释器锁
# 单线单核cpu  多线程之间恶意竞争资源 创造的


"""
互斥锁  排插锁  悲观锁

当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制

线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。

互斥锁为资源引入一个状态:锁定/非锁定

某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

"""

import threading, time

a = 0


def func1(lock):
    # 加锁
    loke.acquire()
    for i in range(10000000):
        global a
        a += 1
    print(a)
    # 释放锁
    loke.release()  # 没有释放就是死锁  如果不能自动结束 则进入僵持状态


def func2(lock):
    loke.acquire()
    for i in range(10000000):
        global a
        a += 1
    print(a)
    loke.release()


if __name__ == '__main__':
    # 创建锁
    loke = threading.Lock()

    # 创建线程
    func1_thread = threading.Thread(target=func1, args=(loke,))
    func2_thread = threading.Thread(target=func2, args=(loke,))
    # 启动线程
    func1_thread.start()
    func2_thread.start()

协程

什么是协程

  • 1)协程微线程,纤程,本质是一个单线程

  • 2)

    协程能在单线程处理高并发,因为遇到IO自动切换
    • 线程遇到I/O操作会等待、阻塞协程遇到I/O会自动切换(剩下的只有CPU操作)
    • 线程的状态保存在CPU的寄存器和栈里而协程拥有自己的空间,所以无需上下文切换的开销,所以快
  • 3)

    为甚么协程能够遇到I/O自动切换
    • greenlet是C语言写的一个模块,遇到IO手动切换
    • 协程有一个gevent模块(封装了greenlet模块),遇到I/O自动切换
  • 4)协程拥有自己的空间,所以无需上下文切换的开销

协程优缺点

  • 协程缺点
    • 无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上
    • 协程如果阻塞掉,整个程序都阻塞
  • 协程最大的优点
    • 不仅是处理高并发(单线程下处理高并发)
    • 特别节省资源(协程本质是一个单线程,当然节省资源)
      • 500日活,用php写需要两百多态机器,但是golang只需要二十多太机器

协程遇到I/O切换,那活只谁干的?

  • 简单说法
    • 协程遇到I/O后自动切换,但是会保持一个socket连接,交给系统内核去处理工作
    • epoll()就工作内核中,他维护了一个链表,来存放所有的socket连接
    • 当内核处理完成后就会回调一个函数,以socket文件描述符为key,结果为value存放到字典中
    • 此时这个列表还是在内核中,需要将这个字典拷贝到用户空间(用户进程中)
  • 本质
    • 1.epoll()中内核则维护一个链表,epoll_wait直接检查链表是不是空就知道是否有文件描述符准备好了。
    • 2.在内核实现中epoll是根据每个sockfd上面的与设备驱动程序建立起来的回调函数实现的。
    • 3.某个sockfd上的事件发生时,与它对应的回调函数就会被调用,来把这个sockfd加入链表,其他处于“空闲的”状态的则不会。
    • 4.epoll上面链表中获取文件描述,这里使用内存映射(mmap)技术,避免了复制大量文件描述符带来的开销
    • 内存映射(mmap):内存映射文件,是由一个文件到一块内存的映射,将不必再对文件执行I/O操作

Python中协程的模块

  • greenlet:遇到I/O手动切换,是一个C模块
  • gevent:对greenlet封装,遇到I/O自动切换借助C语言库greenlet
  • asyncio:和gevent一样,也是实现协程的一个模块(python自己实现

协成是什么

为什么说它是一个执行单元,因为它自带cpu上下文。
这样只要在合适的时机,我们可以把一个协成 切换到另一个协成。
只要这个过程中保存或修复cpu上下文 那么程序还是可以运行的

协程,又称微线程,纤程。英文名Coroutine。
异步lo 

协程的概念很早就提出来了,但直到最近几年才在某些语言(如Lua)中得到广泛应用。

协程看上去是函数,但执行过程中,在函数内部可中断,然后转而执行别的函数,在适当的时候再返回来接着执行。

python可以通过 yield/send 的方式实现协程,也可以使用第三方库中的greenlet来实现协程。

"""

"""
进程是资源分配的单位
线程是操作系统调度的单位
进程切换需要的资源很最大,效率最低
线程切换需要的资源一般,效率一般(当然了在不考虑GTL的情况下)
协成切换任务资源很小,效率高


多进程、多线程根据cpu核数不一样可能是并行的,
但是协成是在一个线程中  所以是并发

通俗的理解:
在一个线程中的某个函数,可以再任何地方保存当前函数的一些临时变量等信息
然后切换到另一个函数中执行,注意不是通过调用函数的方式做到的。
并且切换的次数以及什么时候在切换到原来的函数都由开发者自己确定

协成和线程的差异

在实现多任务时,线程千幻从系统层面远不止保存和恢复cpu上下文这么简单。
操作系统为了程序运行的高效性每个线程都由自己的缓存Cache等等数据
操作系统还会帮你做这些数据的恢复操作。所以线程的切换非常耗性能。
但是协成的切换只是单纯的操作cpu上下文,所以一秒钟切换上百万次系统都抗的住。

创建协程

"""

翔翔的demo

本质上协成只有一个线程
"""
# 在def里面只看到


import time


def fun():
    while True:
        print("work1")
        time.sleep(0.1)
        yield


def fun1():
    while True:
        print("work2")
        time.sleep(0.1)
        yield


s1 = fun()
s2 = fun1()

while True:
    next(s1)
    next(s2)

greenlent模块

greenlet封装的是yield,为了让程序员更好的使用协成

"""

翔翔的demo

greenlet封装的是yield,为了让程序员更好的使用协成
"""

import time

from greenlet import greenlet


def work1():
    for i in range(5):
        print("work1----")
        time.sleep(0.1)
        # 切换执行线程2
        s2.switch()


def work2():
    for i in range(5):
        print("work2----")
        time.sleep(0.1)
        # 切换执行线程1
        s1.switch()


# 创建协成制定对应的任务

s1 = greenlet(work1)
s2 = greenlet(work2)

# 启动并切换到指定协成执行对应的任务
s1.switch()

打补丁找猴子

"""

翔翔的demo

"""

import time, gevent

from gevent import monkey

monkey.patch_all()


def work1():
    print("work1:", gevent.getcurrent())
    for i in range(10):
        print("work1---")
        # gevent.sleep(0.5)
        time.sleep(0.5)


def work2():
    print("work2:", gevent.getcurrent())
    for i in range(10):
        print("work2---")
        gevent.sleep(0.5)
        # time.sleep(0.5)


if __name__ == '__main__':
    s1 = gevent.spawn(work1)
    s2 = gevent.spawn(work2)

    gevent.joinall([s1, s2])

进程,线程,协程爬取页面对比

  • 特点:
    • 1.进程:启用进程非常浪费资源
    • 2.线程:线程多,并且在阻塞过程中无法执行其他任务
    • 3.协程:gevent只用起一个线程,当请求发出去后gevent就不管,永远就只有一个线程工作,谁先回来先处理

for循环

  • 第四:性能最差
import requests
url_list = [
    'https://www.baidu.com',
    'http://dig.chouti.com/',
]

for url in url_list:
    result = requests.get(url)
    print(result.text)

进程池

  • 缺点:启用进程非常浪费资源

multiprocessing.Pool

# -*- coding: utf-8 -*-
import requests
from multiprocessing import Pool

def fetch_request(url):
    result = requests.get(url)
    print(result.text)

def call(arg):
    print('-->exec done:',"测试进程池执行后回调功能")

url_list = [
    'https://www.baidu.com',
    'https://www.google.com/',         #google页面会卡住,知道页面超时后这个进程才结束
    'http://dig.chouti.com/',          #chouti页面内容会直接返回,不会等待Google页面的返回
]

if __name__ == '__main__':
    pool = Pool(10)        # 创建线程池
    for url in url_list:
        #用法1 callback作用是指定只有当Foo运行结束后就执行callback调用的函数,父进程调用的callback函数
        pool.apply_async(func=fetch_request, args=(url,),callback=call)
    print('end')
    pool.close()    #关闭pool
    pool.join()     #进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。

ProcessPoolExecutor

import requests
from concurrent.futures import ProcessPoolExecutor

def fetch_request(url):
    result = requests.get(url)
    print(result.text)

url_list = [
    'https://www.baidu.com',
    'https://www.google.com/',         #google页面会卡住,知道页面超时后这个进程才结束
    'http://dig.chouti.com/',          #chouti页面内容会直接返回,不会等待Google页面的返回
]

if __name__ == '__main__':
    pool = ProcessPoolExecutor(10)        # 创建线程池
    for url in url_list:
        pool.submit(fetch_request,url)    # 去线程池中获取一个进程,进程去执行fetch_request方法
    pool.shutdown(wait=True)

线程池

  • 缺点: 创建一个新线程将消耗大量的计算资源,并且在阻塞过程中无法执行其他任务。
  • 例: 比如线程池中10个线程同时去10个url获取数据,当数据还没来时这些线程全部都在等待,不做事。
import requests
from concurrent.futures import ThreadPoolExecutor

def fetch_request(url):
    result = requests.get(url)
    print(result.text)

url_list = [
    'https://www.baidu.com',
    'https://www.google.com/',         #google页面会卡住,知道页面超时后这个进程才结束
    'http://dig.chouti.com/',          #chouti页面内容会直接返回,不会等待Google页面的返回
]

pool = ThreadPoolExecutor(10)            # 创建一个线程池,最多开10个线程
for url in url_list:
    pool.submit(fetch_request,url)       # 去线程池中获取一个线程,线程去执行fetch_request方法

pool.shutdown(True)                      # 主线程自己关闭,让子线程自己拿任务执行

协程

  • 特点 :gevent只用起一个线程,当请求发出去后gevent就不管,永远就只有一个线程工作,谁先回来先处理
import gevent
from gevent import monkey
monkey.patch_all(select=False)  # 注意,这个导包顺序不要变
import requests

# 这些请求谁先回来就先处理谁
def fetch_async(method, url, req_kwargs):
    response = requests.request(method=method, url=url, **req_kwargs)
    print(response.url, response.content)

# ##### 发送请求 #####
gevent.joinall([
    gevent.spawn(fetch_async, method='get', url='https://www.baidu.com/', req_kwargs={}),
    gevent.spawn(fetch_async, method='get', url='https://www.google.com/', req_kwargs={}),
    gevent.spawn(fetch_async, method='get', url='https://github.com/', req_kwargs={}),
])