首页 > 技术文章 > 【进程-线程-协程】

gokublog 2021-04-20 17:58 原文

多任务-进程/线程/协程

1.多任务的概念

1.1 并行和并发

  • 单核cpu实现多任务
    • 时间片轮转
    • 每个任务执行很短的时间
    • 假的多任务
    • 并发

![image-20210316235023692](/Users/huhao/Library/Application Support/typora-user-images/image-20210316235023692.png)

  • 多核cpu实现多任务
    • 当运行的程序小于cpu核数
    • 不用再去轮转执行
    • 这就是并行

![image-20210316235227274](/Users/huhao/Library/Application Support/typora-user-images/image-20210316235227274.png)

  • 一般情况下都是并发

2.多任务之线程

  • 多线程是实现多任务比较轻便的方式

2.1 多线程的使用步骤

  • 函数的执行
import threading


def test1(g_nums):
		print('test1---xxxxx{0}'.format(g_nums))
    
def test2():
  	print('test2---xxxxx')
    
def main():
  	g_nums = [1,2,3]
  	t1 = threading.Thread(target=test1,args=(g_nums,)) # 通过nums传递参数
    t2 = threading.Thread(target=test2)
		t1.start()	# 线程t1开始执行
    t2.start()	# 线程t2开始执行
		
    print(threading.enumerate()) # 查看进程数量
if __name__ == "__main__":
  	main()
    
  • 类的执行
# 通过继承threading.Thread和重写run()方法可以实现类的线程执行,其原理就是继承
import threading
import time


class Mythread(threading.Thread):
		def run(self):
				for i in range(3):
						time.sleep(1)
						print('myThread----{0}'.format(i))

if __name__ == "__main__":
  t = Mythread()
  t.start
  • target指定执行什么函数
  • args表示传递什么参数 是一个元组

2.2 多线程的创建和死亡

  • 线程的创建
    • 调用线程threading.Thread对象是创建一个对象
    • 只有开始threading.Thread.start()方法的时候线程才会创建并且开始运行
  • 线程的结束
    • 如果threading.Tread指定的函数执行结束,那么这个线程就结束了
    • 主线程会默认等待所有子线程执行结束后它才结束
    • 如果主线程不小心挂了,子线程也就挂了

2.3 线程之间共享全局变量

  • 多任务往往配合使用
    • 所以共享全局变量
  • 在一个函数中对全局变量修改是否要加global要看全局变量的指向是不是改变
    • 如果只是修改了数据,不用加gloabl
    • 如果修改了纸箱则要加global

2.4 多线程的问题-共享全局变量的问题

  • 多线程共享全局变量而且同时操作全局变量 有时候会出现资源竞争

![image-20210317005813818](/Users/huhao/Library/Application Support/typora-user-images/image-20210317005813818.png)

  • 解决方法一:互斥锁

    • 利用原子性(要么不执行要么执行完)
    • 通过互斥锁来解决这个问题
    • 在写操作之前加锁
    • 在操作之后释放锁
    # 创建锁
    metux = threading.lock()
    
    # 加锁
    metux.acquire()
    
    # 释放锁
    metux.release()
    

    ![image-20210317010822185](/Users/huhao/Library/Application Support/typora-user-images/image-20210317010822185.png)

  • 解决方法二:优化 只在写操作的时候加锁子

    ![image-20210317011138982](/Users/huhao/Library/Application Support/typora-user-images/image-20210317011138982.png)

2.5 多线程的问题-多个互斥锁的死锁问题

  • 你等我,我等你就会出现死锁的问题
  • 如何避免死锁
    • 添加超时时间
    • 从程序的角度避免多个人写-银行家算法

3.多任务之进程

3.1 程序和进程

  • 程序:程序就是一个没有执行的类似于xxx.exe的东西
  • 进程:程序的代码+分配的资源就是进程

3.2 进程的状态

  • 新建

  • 就绪

  • 运行

  • 死亡

  • 等待

![image-20210317131843032](/Users/huhao/Library/Application Support/typora-user-images/image-20210317131843032.png)

3.3 多进程的使用步骤

# import threading
import multiprocessing

def test1():
		print('test1---xxxxx')
    
def test2():
  	print('test2---xxxxx')
    
def main():
  	p1 = multiprocessing.Process(target=test1)
    p2 = multiprocessing.Process(target=test2)
		p1.start()	
    p2.start()	
		
if __name__ == "__main__":
  	main()

3.4 多进程的问题

![image-20210317132749018](/Users/huhao/Library/Application Support/typora-user-images/image-20210317132749018.png)

  • 多进程相当于代码copy多份+占用资源共同执行。所以进程的耗费资源很大。
  • copy的信息中还有类似pid这样的信息是不一样的
  • python多进程具有c语言写时拷贝的特点 只有修改的时候才会去copy

3.5 进程之间的通信

3.5.1 socket

3.5.2 queue

  • 简单的队列可以在同台机器上实现进程之间的通信
  • redis及其他高级队列可以跨机器

![image-20210317190019772](/Users/huhao/Library/Application Support/typora-user-images/image-20210317190019772.png)

3.5.3 进程池

  • 相当于先创建好进程 相当于mysql连接池

  • 多个进程的时候用进程池

  • 进程池可以重复利用进程池中的进程

  • 可以创建几个进程要和计算机硬件相关

![image-20210317190438274](/Users/huhao/Library/Application Support/typora-user-images/image-20210317190438274.png)

4.进程和线程的对比

  • 代码-->进程

  • 进程时一坨资源和代码的总和

    • qq多开时多进程
    • 进程之中实现多任务的还是线程
  • 线程

    • 一个qq中的多个功能是线程
    • 一个进程中的多个任务是线程

5.协程

5.1 迭代器

![image-20210318183103647](/Users/huhao/Library/Application Support/typora-user-images/image-20210318183103647.png)

![image-20210318183305098](/Users/huhao/Library/Application Support/typora-user-images/image-20210318183305098.png)

5.1.1 可迭代对象

  • 可迭代对象

    • 列表、元祖、字典、集合、字符串
    • 如果一个对象要是可迭代对象,那它必须有__iter__方法
    from collections import Iterable
    isinstance([11,22,33],Iterable) # True
    isinstance((11,22,33),Iterable) # True
    isinstance({'a':11,'b':22,'c':33},Iterable) # True
    isinstance('112233',Iterable) # True
    

5.1.2 迭代器

![image-20210318183404142](/Users/huhao/Library/Application Support/typora-user-images/image-20210318183404142.png)

  • 迭代器
    • 如果一个对象实现了__iter____next__方法,那它就是一个迭代器

5.1.3 迭代器的应用

  • 要保存数据的两种方式

    • 先创造并保存数据(数据)--- 鱼
    • 先生成一个迭代器(创造数据的工具)---- 鱼竿
  • 斐波那契数列

    • 第一种--列表
    nums = []
    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)
    
    • 第二种--迭代器
    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
    fibo = Fibonacci(10)
    for num in fibo:
    		print(num)
    

5.2 生成器

  • 生成器是特殊的迭代器

  • 生成器的实现方式一:

    [x*2 for x in range(10)] # 迭代器
    (x*2 for x in range(10)) # 生成器
    
    • 函数汇总有yeild自动变成生成器
      • 只要有yeild就是生成器
  • 生成器的实现方式二:

    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)
    res = next(obj)
    print(res)
    

    ![image-20210318214702428](/Users/huhao/Library/Application Support/typora-user-images/image-20210318214702428.png)

5.3 协程-yield

  • 通过yield实现协程-多任务
import time

def task_1():
  	while True:
      	print('-----1-----')
				time.sleep(1)
        yield	# 函数中有yield相当于变成了一个生成器

def task_2():
  	while True:
      	print('-----2-----')
				time.sleep(1)
        yield # 函数中有yield相当于变成了一个生成器
   
def main():
  	t1 = task_1()	# 
    t2 = task_2()
    while True:
      	next(t1)
        next(t2)

if __name__ == "__main__":
 		main()

5.4 协程-greenlet

  • greenlet替换yield
sudo pip3 install greenlet
from greenlet import greenlet
import time

def task_1():
  	while True:
      	print('-----1-----')
				gr2.switch()	# 相当于切换到task_2去执行
        time.sleep(1)


def task_2():
  	while True:
      	print('-----2-----')
				gr1.switch()	# 相当于切换到task_1去执行
        time.sleep(1)
        
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()

5.5 协程-gevent

  • gevent实现协程并发
sudo pip3 install gevent
import gevent

def f(n):
		for i in range(n):
				print(gevent.getcurrent(),i)

g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
g1.join()
g2.join()
g3.join()
  • gevent的特点
    • 遇到延时操作gevent就会切换任务
      • gevent.sleep(0.1)
    • gevent封装了greenlet
    • greenlet封装了yield
  • 给代码打补丁
import gevent
from gevent import monkey

monkey.patch_all()	# 把程序中需要pending的代码自动换成gevent中的代码

def f(n):
		for i in range(n):
				print(gevent.getcurrent(),i)

g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
g1.join()
g2.join()
g3.join()

6.进程、线程、协程对比

  • 进程是资源分配的最小单位

    • 代码+操作系统的资源
  • 线程是操作系统调度的最小单位

  • 进程之间的切需要很大的资源,效率低

  • 线程切换需要的资源一般,效率一般

  • 协程切换任务资源很小、效率更高

  • 进程和线程根据cpu核心数和任务的数量,可能是并行也可能是并发、协程一定是并发、

进程 线程 协程
依赖关系 依赖于进程 依赖于线程
资源消耗 消耗最大 消耗次之 消耗最小

推荐阅读