python 多任务、线程、协程

本文介绍了Python中的多任务实现,包括多进程和多线程的概念及应用。多进程通过queue队列实现进程间通信,使用进程池节约资源。线程作为CPU调度的最小单位,可以通过Thread模块创建并传入参数,使用join方法和.setDaemon()方法进行控制。还探讨了线程间共享全局变量的问题和解决方案。最后,文章提到了greenlet和gevent模块在协程中的应用,以及一个简单的协程图片加载器的示例。
摘要由CSDN通过智能技术生成

 

多任务:

初始

多任务就是同一时刻多个任务同时执行,例如开演唱会时明星一边唱歌一边跳舞,开车时眼睛看路手操作方向盘。这些都是多任务场景。

对于电脑来说多任务就是同时运行多个应用程序,例如qq、微信、浏览器等等同时在电脑上运行。

  1. 电脑实现多任务的原理

例如qq、微信、网易云音乐播放器3个应用程序能同时运行是因为CPU在多个应用程序之间高速切换的结果,当CPU切换到了qq,就用0.01s时间(时间不确定)执行qq程序,然后再随机切换到其他应用程序在执行一段时间,CPU在多个程序之间快速往复执行,我们的肉眼根本感觉不到卡顿,导致我们的错觉感觉是同时运行的效果。如果电脑运行了多个程序有时候会出现卡顿现象是因为cup切换不过来了。

多任务就是同一时刻多个任务同时执行,例如开演唱会时明星一边唱歌一边跳舞,开车时眼睛看路手操作方向盘。这些都是多任务场景。

对于电脑来说多任务就是同时运行多个应用程序,例如qq、微信、浏览器等等同时在电脑上运行。

  1. 电脑实现多任务的原理

例如qq、微信、网易云音乐播放器3个应用程序能同时运行是因为CPU在多个应用程序之间高速切换的结果,当CPU切换到了qq,就用0.01s时间(时间不确定)执行qq程序,然后再随机切换到其他应用程序在执行一段时间,CPU在多个程序之间快速往复执行,我们的肉眼根本感觉不到卡顿,导致我们的错觉感觉是同时运行的效果。如果电脑运行了多个程序有时候会出现卡顿现象是因为cup切换不过来了。

 拿程序看一下:

不使用多任务:

import time


def sing():
    for i in range(3):
        print('正在唱歌...', i)
        time.sleep(1)


def dance():
    for i in range(3):
        print('正在跳舞...', i)
        time.sleep(1)


def main():
    sing()
    dance()


if __name__ == '__main__':
    main()
正在唱歌... 1
正在唱歌... 2
正在唱歌... 3
正在跳舞... 1
正在跳舞... 2
正在跳舞... 3

使用多任务:

import time, multiprocessing


def sing():
    for i in range(3):
        print('正在唱歌...', i + 1)
        time.sleep(1)


def dance():
    for i in range(3):
        print('正在跳舞...', i + 1)
        time.sleep(1)


def main():
    # 创建进程
    # target 指向的是执行的目标
    p1 = multiprocessing.Process(target=sing)
    p2 = multiprocessing.Process(target=dance)
    # 开始进程
    p1.start()
    p2.start()


if __name__ == '__main__':
    main()
正在唱歌... 1
正在跳舞... 1
正在唱歌... 2
正在跳舞... 2
正在唱歌... 3
正在跳舞... 3

多进程:

我们想通过酷我听歌,具体的过程应该是先找到酷我应用程序,然后双击就会播放音乐。

当我们双击的时候,操作系统将程序装载到内存中,操作系统为它分配资源,然后才能运行。运行起来的应用程序就称之为进程。也就是说当程序不运行的时候我们称之为程序,当程序运行起来他就是一个进程。通俗的理解就是不运行的时候是程序,运行起来就是进程。程序和进程的对应关系是:程序只有一个,但是进程可以有多个。

多进程queue队列:

在是用queue队列时,导入模块有两种方式:一、import multiprocessing  二、import queue

import multiprocessing
import queue

# 可以写参数,如果不写可以放任何数据
# 指定参输后,只能从存放指定的个数数据
q = queue.Queue(3)  # ----->导入的是queue模块
# q = multiprocessing.Queue(3)# --------->导入的是multiprocessing模块
q.put(2)
q.put(4)
q.put(6)
# q.put(5)  #------>阻塞了,知道数据有被使用后,才能继续被放入,否则一直等待放入
# q.put_nowait(8)  # ---->不等待直接抛出异常
print(q.get())  # ----->get是获取队列中的值
print(q.get())
print(q.get())
# q.get()  # 如果队列中没有了,则会一直等待,直到有数据才执行
# q.get_nowait()  # 不等待,直接抛出异常
print(q.empty())  # 判断队列中是否还有数据,如果没有了则返回True
print(q.full())  # 判断队列是否满了,满了则返回True

使用多进程可以节约资源、并且可以传入参数:

import time
import multiprocessing


def sing(num):
    for i in range(num):
        print('正在唱歌...', i)
        time.sleep(1)


def dance(num):
    for i in range(num):
        print('正在跳舞...', i)
        time.sleep(1)


def main():
    # 创建进程
    # target 指向的是执行的目标   args 指向的是num的值
    p1 = multiprocessing.Process(target=sing, args=(3,))
    p2 = multiprocessing.Process(target=dance, args=(3,))
    p1.start()
    p2.start()


if __name__ == '__main__':
    main()
正在唱歌... 0
正在跳舞... 0
正在唱歌... 1
正在跳舞... 1
正在唱歌... 2
正在跳舞... 2

进程间的通信:

刚才我们说了进程可以理解为复制了一份程序有加载到了内存了,进程之间是独立的,如果我想两个进程之间进行通讯怎么办呢?我们可以使用Queue 队列,队列是一种先进先出的存储数据结构,就比如排队上厕所一个道理。

两个进程通讯,就是一个子进程往queue中写内容,另一个进程从queue中取出数据。就实现了进程间的通讯了。

from multiprocessing import Process, Queue


# import time


def down_data(q):
    lst = [1, 2, 3]
    for i in lst:
        q.put(i)  # 将提取的内容放到队列中


def process_data(q):
    for i in range(q.qsize()):  # 获取队列的长度
        print(q.get())  # 提取队列中的内容


def main():
    # 创建一个队列
    q = Queue()
    # 创建两个子进程
    q1 = Process(target=down_data, args=(q,))
    q2 = Process(target=process_data, args=(q,))
    q1.start()
    # time.sleep(2)
    q2.start()


if __name__ == '__main__':
    main()
1
2
3

进程池:

当需要创建的子进程数量不多时,我们可以直接利用multiporcessing中的Process动态生成多个进程,但是如果现在有100个任务需要处理,那我们需要多少个子进程呢,如果我们创建100个子进程也可以实现,但是资源比较浪费。我们也可以创建指定个数个子进程,例如只创建10个子进程,让着10个子进程重复的执行任务,这样就节约了资源。

就比如我们去景区湖上游玩,游船是重复利用的。

import time
from multiprocessing import Process, Pool


def foo(i):
    print('i的值是', i)
    time.sleep(3)
    print('end...')


if __name__ == '__main__':
    # for i in range(5):
    #     p1 = Process(target=foo, args=(i,))
    #     p1.start()

    pool = Pool(3)  # 创建进程池对象
    for i in range(5):
        pool.apply_async(func=foo, args=(i,))  # 将任务添加到进程池中
    pool.close()  # 关闭进程池,等待所有进程结束后才关闭
    pool.join()  # 主进程等待所有子进程执行完毕后才执行主进程,必须在close后

i的值是 0
i的值是 1
i的值是 2
end...
i的值是 3
end...
i的值是 4
end...
end...
end...

线程:

由于进程是资源拥有者,创建、撤消与切换存在较大的内存开销,因此需要引入轻型进程

即线程,

进程是资源分配的最小单位,线程是CPU调度的最小单位(程序真正执行的时候调用的是线程).每一个进程中至少有一个线程。

使用Thread模块创建线程:

import time
import threading


def sing():
    for i in range(3):
        print('正在唱歌...', i + 1)
        time.sleep(1)


def dance():
    for i in range(3):
        print('正在跳舞...', i + 1)
        time.sleep(1)


def main():
    t1 = threading.Thread(target=sing)  # 创建t1子线程,执行sing函数
    t2 = threading.Thread(target=dance)
    t1.start()  # 进程开始
    t2.start()


if __name__ == '__main__':
    main()
    print('程序结束了...')
正在唱歌... 1
正在跳舞... 1
程序结束了...
正在唱歌... 2
正在跳舞... 2
正在跳舞... 3
正在唱歌... 3

传入参数:

import time
import threading


def sing(num):  # 传参
    for i in range(num):
        print('正在唱歌...', i + 1)
        time.sleep(1)


def dance(num):
    for i in range(num):
        print('正在跳舞...', i + 1)
        time.sleep(1)


def main():
    t1 = threading.Thread(target=sing, args=(3,))  # 创建t1子线程,执行sing函数
    t2 = threading.Thread(target=dance, args=(3,))
    t1.start()  # 进程开始
    t2.start()


if __name__ == '__main__':
    main()
    print('程序结束了...')

join方法:

join方法:只有等待一个子线程结束完以后,才能继续运行下一个子线程
import time, threading


def sing():
    for i in range(3):
        print('正在唱歌...', i)
        time.sleep(1)


def dance():
    for i in range(3):
        print('正在跳舞...', i)
        time.sleep(1)


def main():
    t1 = threading.Thread(target=sing)  # 创建t1子线程,执行sing函数
    t2 = threading.Thread(target=dance)
    t1.start()  # 进程开始
    t1.join()  # t1子线程执行完后,t2和主线程同时执行
    t2.start()
    # t1.join()  # 效果:t1,t2子程序执行完后,主线程才开始执行


if __name__ == '__main__':
    main()
    print('程序结束了...')

.setDaemon() 方法:

setDaemon方法:用来守护主线程,当主线程执行完毕后,子线程不会继续执行
如果设置一个子线程守护,那么程序将会正常运行
import time,threading


def sing():
    for i in range(3):
        print('正在唱歌...', i)
        time.sleep(1)


def dance():
    for i in range(3):
        print('正在跳舞...', i)
        time.sleep(1)


def main():
    t1 = threading.Thread(target=sing)  # 创建t1子线程,执行sing函数
    t2 = threading.Thread(target=dance)
    t1.setDaemon(True)  # 设置t1、t2守护主线程,当主线程执行完后,子线程不会继续往下执行
    # t2.setDaemon(True)
    t1.start()  # 进程开始
    t2.start()


if __name__ == '__main__':
    main()
    print('程序结束了...')

实例方法:

- getName(): 获取线程的名称。

- setName(): 设置线程的名称。

- isAlive(): 判断当前线程存活状态。

import time,threading


def sing(num):  # 传参
    for i in range(num):
        print('正在唱歌...', i + 1)
        time.sleep(1)


def dance(num):
    for i in range(num):
        print('正在跳舞...', i + 1)
        time.sleep(1)


def main():
    t1 = threading.Thread(target=sing, args=(3,))
    t2 = threading.Thread(target=dance, args=(3,))
    t1.start()
    print(t1.is_alive())  # ---->True
    t2.start()
    # t1.setName('11')  # 更改线程名称
    print(t1.getName())  # ---->Thread-1


if __name__ == '__main__':
    main()
    print('程序结束了...')

使用继承开启线程:

1.定义一个类继承threading.Thread类。
2.复写父类的run()方法。
import threading


class MyThraed(threading.Thread):
    def __init__(self, num):
        super().__init__()
        self.num = num

    def run(self):
        for i in range(self.num):
            print('...run...', i)


if __name__ == '__main__':
    mythread = MyThraed(3)
    mythread.start()
...run... 0
...run... 1
...run... 2

线程之间共享全局变量:

import threading, time

g_num = 10


def test1():
    global g_num
    g_num += 1
    print('test1---->', g_num)


def test2():
    print('test2---->', g_num)


def main():
    t1 = threading.Thread(target=test1)
    t2 = threading.Thread(target=test2)
    t1.start()
    time.sleep(1)
    t2.start()


if __name__ == '__main__':
    main()
test1----> 11
test2----> 11

共享全局变量问题:

多线程开发的时候共享全局变量会带来资源竞争效果。也就是数据不安全。

当数据足够大时会丢失数据,那我们来看一下:

#当传入参数为100时:
import threading, time

num = 0


def test1(num1):
    global num
    for i in range(num1):
        num += 1
    print('test1---->', num)


def test2(num1):
    global num
    for i in range(num1):
        num += 1
    print('test1---->', num)


def main():
    t1 = threading.Thread(target=test1, args=(100,))
    t2 = threading.Thread(target=test2, args=(100,))
    t1.start()
    time.sleep(1)
    t2.start()


if __name__ == '__main__':
    main()
#结果为:
test1----> 100
test1----> 200

当数据变为10000000时:

import threading, time

num = 0


def test1(num1):
    global num
    for i in range(num1):
        num += 1
    print('test1---->', num)


def test2(num1):
    global num
    for i in range(num1):
        num += 1
    print('test1---->', num)


def main():
    t1 = threading.Thread(target=test1, args=(10000000,))
    t2 = threading.Thread(target=test2, args=(10000000,))
    t1.start()
    time.sleep(1)
    t2.start()


if __name__ == '__main__':
    main()
# 结果为:
test1----> 10384512
test1----> 18449412

由此我们可以看出当数据足够大时,数据会丢失。

那么我们怎么进行解决呢,可以使用互斥锁将数据“锁起来”,这样数据就不会丢失了。

import threading, time

num = 0


def test1(num1):
    global num
    lock.acquire()  # 加锁
    for i in range(num1):
        num += 1
    lock.release()  # 解锁
    print('test1---->', num)


def test2(num1):
    global num
    lock.acquire()
    for i in range(num1):
        num += 1
    lock.release()
    print('test1---->', num)


def main():
    t1 = threading.Thread(target=test1, args=(10000000,))
    t2 = threading.Thread(target=test2, args=(10000000,))
    t1.start()
    time.sleep(1)
    t2.start()


lock = threading.Lock()  # 创建锁对象
if __name__ == '__main__':
    main()
test1----> 10000000
test1----> 20000000

协程:

greenlet 模块:

greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator

安装 :pip3 install greenlet。如果引入的时候还是报错,使用pycharm进行下载安装,

选择 file --> settings --> Project:项目名称 --> Project Interpreter-->…

import greenlet
import time


def test1():
    while True:
        print('---test1---')
        time.sleep(0.5)
        g2.switch()  # 2.当执行完test1 会去执行test2


def test2():
    while True:
        print('---test2---')
        time.sleep(0.5)
        g1.switch()  # 3.当执行完test2 会去执行test1


if __name__ == '__main__':
    g1 = greenlet.greenlet(test1)  # 指定g1的执行目标
    g2 = greenlet.greenlet(test2)

    g1.switch()  #1. 让程序去g1指定的目标去执行

gevent模块

import gevent
import time
from gevent import monkey

# 所有;补丁
monkey.patch_all()


# 如果程序中没有耗时操作就顺序执行
def test1():
    for i in range(0, 5):
        print('test1------>', i)
        time.sleep(0.5)


def test2():
    for i in range(0, 5):
        print('test2------>', i)
        time.sleep(0.5)


if __name__ == '__main__':
    gevent.joinall([gevent.spawn(test1),
                    gevent.spawn(test2)])

结果为:

test1------> 0
test2------> 0
test1------> 1
test2------> 1
test1------> 2
test2------> 2
test1------> 3
test2------> 3
test1------> 4
test2------> 4

协程小拓展:图片加载器

import requests
import gevent


# url = 'https://b-ssl.duitang.com/uploads/item/201612/10/20161210005234_hZzLn.jpeg'
# response = requests.get(url)
# # response.content  # 获取图片二进制数据
#
# with open('img/1.jpg', mode='wb')as f:
#     f.write(response.content)

def downloader(url, name):
    response = requests.get(url)
    with open('img/' + name, mode='wb')as f:
        f.write(response.content)


if __name__ == '__main__':
    url = 'https://b-ssl.duitang.com/uploads/item/201806/16/20180616111135_gnmcq.jpeg'
    url1 = 'https://b-ssl.duitang.com/uploads/item/201812/30/20181230142429_qmkvs.jpg'
    gevent.joinall([
        gevent.spawn(downloader, url, '寻欢作乐.jpeg'),
        gevent.spawn(downloader, url1, '暴富.jpg')
    ])

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值