Python(20)进程、线程、协程

一、进程

进程  包含  线程  包含  协程

打开一个程序,产生一个进程,进程是程序的实体
一个任务就是一个进程
进程优点:稳定性高,一个进程崩溃了,不会影响其它进程
进程缺点:1、创建进程开销巨大
2、操作系统能同时运行进程数目有限

from multiprocessing import Process
process = Process(target=函数, name=进程的名字, args=(给函数传递的参数))
process 对象
对象调用方法:
process.start()     启动进程并执行任务
process.run()       只是执行了任务,但没有启动进程
process.terminate() 终止进程

多进程对于全局变量访问,在每一个全局变量里面都放一个m变量,
保证每个进程访问变量互不干扰。
m = 1 不可变类型
list1 = []  可变类型

1、进程的创建

import os
from multiprocessing import Process
from time import sleep

m = 1  #不可变类型
list1 = []  #可变类型

def task1(s, name):
    global m
    while True:
        sleep(s)
        m += 1
        #os.getpid()  进程号码    os.getppid()  父类进程号码
        # print("这是任务1......", os.getpid(), '------', os.getppid(), name)
        list1.append(str(m) + 'task1')
        print("这是任务1......", m, list1)

def task2(s, name):
    global m
    while True:
        sleep(s)
        m += 1
        # print('这是任务2...', os.getpid(), '------', os.getppid(), name)
        list1.append(str(m) + 'task2')
        print('这是任务2...', m, list1)

#主进程全局变量
number = 1
if __name__ == '__main__':
    #主进程pid号码
    print(os.getpid())
    #子进程p
    p = Process(target=task1, name='任务1', args=(1, 'aa'))
    p.start()
    print(p.name)
    #子进程p1
    p1 = Process(target=task2, name='任务2', args=(2, 'bb'))
    p1.start()
    print(p1.name)

    while True:
        number += 1
        sleep(0.2)
        if number == 100:
            p.terminate()
            p1.terminate()
            break
        else:
            print(number)

 

2、进程的自定义

from multiprocessing import Process

class MyProcess(Process):
    def __init__(self, name):
        super(MyProcess, self).__init__()
        self.name = name

    #重写run方法
    def run(self):
        n = 1
        while True:
            # print('进程名字:' + self.name)
            print('{}------>自定义进程, n:{}'.format(n, self.name))
            n += 1

if __name__ == '__main__':
    mp = MyProcess()
    mp.start()    #1、开新的进程  2、run()

    mp1 = MyProcess()
    mp1.start()

 

3、进程池    process-pool

当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态生成多个进程,但如果是上百甚至上千个目标,
手动的去创建进程的工作量巨大,此时就可以用到multiprocess模块提供的Pool方法,
初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满
那么就会创建一个新的进程用来执行该请求,但如果池中的进程数已经达到指定的最大值,
那么该请求就会等待,直到池中有进程结束,才会创建新的进程来执行。

非阻塞式: 全部添加到队列中,立刻返回,并没有等待其他的进程完毕,
但是回调函数是等待任务完成之后才调用。
进程池  设置进程数量  进程复用

(1)阻塞式    添加一个任务,执行任务,如果任务没有结束,任务无法增加

阻塞式特点:

添加一个任务,执行一个任务,如果一个任务不结束,另一个任务进不来

from multiprocessing import Pool
import time
from random import random
import os

def task(task_name):
    print("开始做任务,", task_name)
    start = time.time()
    #使用sleep()
    time.sleep(random() * 2)
    end = time.time()
    print('完成任务:{}用时:{},进程id:{}'.format(task_name, (end - start), os.getpid()))

if __name__ == '__main__':
    pool = Pool(5)
    tasks = ['听音乐', '吃饭', '洗衣服', '玩游戏', '散步', '睡觉', '做饭']
    for task1 in tasks:
        pool.apply(task, args=(task1,))

    pool.close()
    pool.join()
    print('over......')

 

(2)非阻塞式

from multiprocessing import Pool
import time
from random import random
import os

def task(task_name):
    print("开始做任务,", task_name)
    start = time.time()
    #使用sleep()
    time.sleep(random() * 2)
    end = time.time()
    # print('完成任务:{}用时:{},进程id:{}'.format(task_name, (end - start), os.getpid()))
    return '完成任务:{}用时:{},进程id:{}'.format(task_name, (end - start), os.getpid())

container = []
#回调
def callback_func(n):
    container.append(n)

if __name__ == '__main__':
    pool = Pool(5)  #池只能装5个,Pool与主进程同执行,主进程完成,Pool也完成
    tasks = ['听音乐', '吃饭', '洗衣服', '玩游戏', '散步', '睡觉', '做饭']
    for task1 in tasks:
        #池的非阻塞式
        pool.apply_async(task, args=(task1,), callback=callback_func)
    pool.close()  #添加任务结束
    pool.join()  #阻挡主进程,并插入到主进程前面

    for c in container:
        print(c)

    print('over...')

总结进程池:

pool = Pool(max)  创建进程池对象
pool.apply()  阻塞式
pool.apply_async()  非阻塞式

pool.close()
pool.join()  让主进程让步

 

4、进程间的通信    使用queue

进程1    通过queue,将数据传递到  进程2

stack  栈  push pop  先进后出

queue  队列  FIFO  先进先出

from multiprocessing import Queue
from multiprocessing import Process
from time import sleep

q = Queue(5)
q.put('A')
q.put('B')
q.put('C')
q.put('D')
q.put('E')
print(q.qsize())  #队列的长度
if q.full():  #判断队列是否已满  q.empty() 判断队列是否是空的
    print('队列已满')
else:
    q.put('F', timeout=3)  # 队列满了,等待  put()如果队列满了,则只能等待,除非有'空地'则添加成功

#获取队列的值
print(q.get(timeout=2))
print(q.get(timeout=2))
print(q.get(timeout=2))
print(q.get(timeout=2))
print(q.get(timeout=2))
print(q.get(timeout=2))
print(q.get(timeout=2))  #报错 _queue.Empty  没有值

q.put_nowait()
q.get_nowait()
from multiprocessing import Queue
from multiprocessing import Process
from time import sleep

def download(q):
    images = ['girl.jpg', 'boy.jpg', 'man.jpg']
    for image in images:
        print('正在下载:', image)
        sleep(0.5)
        q.put(image)

def getfile(q):
    while True:
        try:
            file = q.get(timeout=5)
            print('{}保存成功!'.format(file))
        except:
            print('全部保存完毕!')
            break

if __name__ == '__main__':
    q = Queue(5)
    p1 = Process(target=download, args=(q,))
    p2 = Process(target=getfile, args=(q,))

    p1.start()
    p1.join()
    p2.start()
    p2.join()

    print('0000000')

 

二、线程

进程: Process

1、线程: Thread

线程:线程是进程中的一个实体,是程序执行流的最小单元 多线程 multithread 一个进程中的多个线程之间可以并发执行 一个程序:一个进程,启动程序 一个线程各干各的

进程是由操作系统找CPU分配空间(内存) 线程依赖于进程,可以执行例如耗时操作

考虑 创建线程? 如何使用线程? t1 = threading.Thread(target=download, name='aa', args=(1,)) 创建 t1.start() 运行

线程状态:新建 start 就绪 运行 结束 sleep()阻塞后,回到就绪 线程是可以共享全局变量的

from time import sleep
import threading

def download(n):
    images = ['girl.jpg', 'boy.jpg', 'man.jpg']
    for image in images:
        print('正在下载:', image)
        sleep(0.6)
        print('下载{}成功!'.format(image))

def listenMusic():
    musics = ['aaa', 'bbb', 'ccc', 'ddd']
    for music in musics:
        print('正在听{}歌曲!'.format(music))

if __name__ == '__main__':
    #线程对象
    t1 = threading.Thread(target=download, name='aa', args=(1,))
    t1.start()

    t2 = threading.Thread(target=listenMusic, name='bb')
    t2.start()

    n = 1
    while True:
        print(n)
        sleep(3)
        n += 1

 

2、线程是共享全局变量的

GIL  global iterable lock  全局解释器锁

数据不安全  线程同步速度慢

添加lock后,数据安全

python底层只要使用线程,默认加锁 进程:计算密集型(计算量大),运算快速 线程:耗时操作,网络下载,爬虫,IO

import threading

ticket = 1000

def pay_ticket():
    global ticket
    for i in range(100):
        ticket -= 1

if __name__ == '__main__':
    t1 = threading.Thread(target=pay_ticket, name='th1')
    t2 = threading.Thread(target=pay_ticket, name='th2')
    t3 = threading.Thread(target=pay_ticket, name='th3')
    t4 = threading.Thread(target=pay_ticket, name='th4')

    t1.start()
    t2.start()
    t3.start()
    t4.start()

    t1.join()
    t2.join()
    t3.join()
    t4.join()

    print('ticket_number', ticket)

 

3、共享数据

如果多个线程共同对某个数据修改,则可能出现不可预料的结束,为了保证数据的正确性,需要对多个线程进行同步。

同步:一个一个的完成,一个做完,另一个才能进来

效率就会降低。

使用Thread对象的Lock和Rlock可以实现简单的线程同步, 这两个对象都有acquire方法和release方法,对于哪些需要每次只 允许一个线程操作的数据,可以将其操作放到acquire和release方法之间。

多线程的优势在于可以同时运行多个任务(至少感觉起来是这样),但是当线程需要共享数据时, 可能存在数据不同步的问题,为了避免这种情况,引入了锁的概念。

lock = threading.Lock()

lock.acquire()    请求得到锁

......

lock.release()    释放锁

只要不释放,其它的线程都无法进入运行状态

import threading
import random
import time

lock = threading.Lock()
list1 = [0] * 10

#列表赋值
def task1():
    #获取线程锁,如果已经上锁,则阻塞并等待锁的释放
    lock.acquire()  #阻塞
    for i in range(len(list1)):
        list1[i] = 1
        time.sleep(0.5)

    lock.release()  #释放

#打印列表值
def task2():
    lock.acquire()
    for i in range(len(list1)):
        print('--->', list1[i])
        time.sleep(0.5)
    lock.release()

if __name__ == '__main__':
    t1 = threading.Thread(target=task1)
    t2 = threading.Thread(target=task2)

    t2.start()
    t1.start()

    t2.join()
    t1.join()

    print('over', list1)

 

4、死锁

开发过程中使用线程,在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。 尽管死锁很少发生,但一旦发生就会造成应用的停止响应,程序不做任何事情。

资源分配先后顺序不当,会造成死锁,应当避免死锁

解决死锁: 1、重构代码 2、使用timeout参数

from threading import Thread, Lock
import time
lockA = Lock()
lockB = Lock()

class MyThread(Thread):
    def run(self):  #start
        if lockA.acquire():  #如果可以获取到锁,则返回True
            print(self.name + '获取了A锁')
            time.sleep(0.1)
            if lockB.acquire(timeout=5):
                print(self.name + '又获取了B锁')
                lockB.release()
            lockA.release()

class MyThread1(Thread):
    def run(self):  #start
        if lockB.acquire():  #如果可以获取到锁,则返回True
            print(self.name + '获取了A锁')
            time.sleep(0.1)
            if lockA.acquire(timeout=5):
                print(self.name + '又获取了B锁')
                lockA.release()
            lockB.release()

if __name__ == '__main__':
    t1 = MyThread()
    t2 = MyThread1()

    t1.start()
    t2.start()

 

5、生产者与消费者    两个线程之间的通信

一个线程为生产者,另一个线程为消费者

Python的queue模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出) 队列Queue,LIFO(先入后出)
队列 LifoQueue,和优先级队列PriorityQueue。这些队列都实现了锁原语(可以理解为原子操作,即要么不做,要么就做完),能够在线程中直接使用。

可以使用队列来实现线程间的同步

import threading
import queue
import random
import time

#生产者
def produce(q):
    i = 0
    while i < 10:
        num = random.randint(1, 100)
        q.put("生产者产生的数据:%d" % num)
        print("生产者产生的数据:%d" % num)
        time.sleep(1)
        i += 1
    q.put(None)
    #完成任务
    q.task_done()

#消费者
def consume(q):
    while True:
        item = q.get()
        if item is None:
            break
        print("消费者获取到:%s" % item)
        time.sleep(4)
    #完成任务
    q.task_done()

if __name__ == '__main__':
    q = queue.Queue(10)
    arr = []

    #创建生产者
    th = threading.Thread(target=produce, args=(q,))
    th.start()

    #创建消费者
    tc = threading.Thread(target=consume, args=(q,))
    tc.start()

    th.join()
    tc.join()
    print('END')

 

6、总结线程    Thread

1、创建线程
内置线程:
t = Thread(target=func, name='', args=(), kwargs={})--->新建状态
t.start()--->就绪状态
run()
join()  主线程让步

自定义线程:
class MyThread(Thread):
    def __init__(self, name):
        super().__init__()
        self.name = name
    def run(self):
        任务

t = MyThread()
t.start()

2、数据共享
进程的共享的线程共享数据区别:
进程是每个进程都有一份
线程是共用一个数据--->数据安全性问题

GIL--->伪线程
lock = Lock()
lock.acquire()
...
lock.release()

只要用锁:顺序有问题  死锁
避免死锁

3、线程间通信:生产者与消费者
生产者:线程
消费者:线程
import queue
q = queue.Queue()

创建生产者
th = threading.Thread(target=func, name='', args=(), kwargs={})
th.start()

tc = threading.Thread(target=func, name='', args=(), kwargs={})
tc.start()

q.put()  放值
q.get()  取值

查看GIL文章

 

三、协程

协程:微线程
进程  线程  协程
在线程中开启多个任务

Process  Thread  底层为生成器完成

协程:耗时操作  高效利用CPU
耗时操作:网络请求(网络下载(爬虫)、上传)、
IO操作(文件的读写)、sleep阻塞
高效利用CPU,高效利用资源
不同执行操作的切换

生成器;yield

import time
def task1():
    for i in range(3):
        print('A' + str(i))
        yield
        time.sleep(0.2)

def task2():
    for i in range(3):
        print('B' + str(i))
        yield
        time.sleep(0.2)

#两个任务交替运行
if __name__ == '__main__':
    g1 = task1()
    g2 = task2()

    while True:
        try:
            next(g1)
            next(g2)
        except:
            break

使用greenlet,可以完成协程任务,并不用写yield

pip install greenlet

import time
from greenlet import greenlet
def a():  #任务A
    for i in range(5):
        print('A' + str(i))
        gb.switch()
        time.sleep(0.1)


def b():  # 任务B
    for i in range(5):
        print('B' + str(i))
        gc.switch()
        time.sleep(0.1)


def c():  # 任务C
    for i in range(5):
        print('C' + str(i))
        ga.switch()
        time.sleep(0.1)

if __name__ == '__main__':
    ga = greenlet(a)
    gb = greenlet(b)
    gc = greenlet(c)

    ga.switch()

 

四、总结进程、线程

进程,线程
Process类
创建进程
def func(n):
    pass
p = Process(target=func, name='', args=(1,), kwargs='')
p.start()
run

class MyProcess(Process):
    def run(self):
        pass
p = MyProcess()
p.start()

进程数据共享

进程池:Pool  可以创建多个进程
p = Pool(5)
阻塞式     apply(func, args, kwargs)   一个一个执行
非阻塞式   apply_async(func, args, kwargs, callback='')    根据最大容量将进程一起添加到里面执行
callback 进程完成后调用的函数,例如下载图片后在callback函数中渲染到手机上

进程间通信:
Queue()
q = Queue(5)
q.put()
q.get()
q.qsize()
q.empty()
q.full()

线程:
进程里面可以存着多个线程,多个线程共用进程资源
t = Thread(target=func, name='', args=(1,), kwargs='')
t.start()

GIL:全局解释器锁
Python自带,线程:伪线程  加锁不能提高效率
快速计算,用进程
耗时操作:用线程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值