Python基础10——线程、进程、协程

半臻
• 阅读 2603

18 线程

18.1 进程和线程

进程:打开一个程序至少就会有一个进程。操作系统进行资源分配的基本单位

线程:线程是CPU调度的基本单位,每个进程至少都有一个线程。

单线程:只有一个线程

def funa():
    print(123)

def funb():
    print(456)
funa()
funb()

# 先执行funa
# 再执行funb

多线程

线程模块: threading

import threading

线程类Thread参数:

​ target:执行的任务名

​ args: 以元组的形式给执行任务传参

def funa():
    print(123)
    time.sleep(2)
    print("结束了")

def funb():
    print(456)
    time.sleep(3)
    print("结束了")

if __name__ == "__main__":
    # 1创建子线程
    t1 = threading.Thread(target=funa) #funa是个函数名
    t2 = threading.Thread(target=funb)

    # 2.开启子线程
    t1.start()
    t2.start()

带参数的执行

def funa(a):
    print("你好吗",a)
    time.sleep(2)
    print("我很好")

def funb(b):
    print(b)
    time.sleep(2)
    print("有点甜")


if __name__ == "__main__":
    # 第一种传入参数的方式
    f1 = Thread(target=funa,args=("传入参数",))
    f2 = Thread(target=funa,args=("传入参数2",))

    f1.start()
    f2.start()


    # 第二种传入参数的方式
    f1 = Thread(target=funa,kwargs={"a":"参数啊"})
    f2 = Thread(target=funa,kwargs={"b":"参数2"})

    f1.start()
    f2.start()


18.2 线程

步骤

  1. 创建子线程 Thread()
  2. 开启子线程

18.2.1 守护线程 、阻塞线程

守护线程:主线程执行完,子线程立刻结束

阻塞线程:等待join_list中的子线程执行完,再执行主线程

def funa():
    print("开始a")
    time.sleep(2)
    print("结束a")

def funb():
    print("开始b")
    time.sleep(2)
    print("结束b")

if __name__ == "__main__":
    t1 = threading.Thread(target=funa)
    t2 = threading.Thread(target=funb)

    #开启守护线程,主线程执行完,子线程也会跟着结束
    t1.setDaemon(True)
    t2.setDaemon(True)

    t1.start()
    t2.start()

    # 阻塞主线程,暂停的作用,只有join的执行完,才会执行主线程
    t1.join()
    t2.join()

    t1.setName("线程1")
    t2.setName("线程2")

    # 获取线程名字
    t1.getName()
    t2.getName()

    print("这是主线程,程序的最后一行")

18.2.2 线程的执行顺序的无序的

两个任务是一起执行的,线程之间的执行是无序的

def test():
    time.sleep(1)
    print("当前的线程是",threading,current_thread())


if __name__ =="__main__":
    for i in range(5):
        # 创建子线程
        s1 = threading.Thread(target=test)
        s1.start()

18.2.3 创建线程类

线程执行代码的封装

  1. 继承Thread类
  2. 重写run方法
from threading import Thread

# 定义一个线程类
class Mythread(Thread):

    # 重写run方法,规定run这个名字,表示线程活动的方法
    def run(self):
        print("面向对象")
        time.sleep(3)
        print("线程")

if __name__ == "__main__":
    my = MyThread()
    my.start()

18.2.4 资源共享

# 资源共享
from threading import Thread #导入线程模块
import time

li = []
# 写入数据
def wdata():
    for i in range(5):
        li.append(i)
        time.sleep(0.2)
    print("写入的数据是:",li)

# 读取数据
def rdata():
    print("读取的数据是:",li)

if __name__ == "__main__":
    wd = Thread(target=wdata)
    rd = Thread(target=radata)
    wd.start()

    wd.join() #只有等待写入完毕,才可以执行后面的代码
    rd.start()


    print("这是最后一行")

18.2.5 资源共享导致资源竞争

a是共享的资源,导致add和add2两个线程去竞争资源a,导致结果不一样。

from threading import Thread

a = 0
n = 1000000
# 循环b次给全局变量a加1
def add():
    for i in range(n):
        global a  #global 声明全局变量
        a += 1
    print("第一次",a)

def add2():
    for i in range(n):
        global a  #global 声明全局变量
        a += 1
    print("第二次",a)


if __name__ == "__main__":
    # 创建两个子线程
    first = Thread(target=add)
    second = Thread(target=add2)

    # 启动线程
    first.start()
    second.start()

# 运行结果
# 第一次 1008170
# 第二次 1509617

18.2.6 线程同步的方式

  1. 线程等待(join)

  2. 互斥锁

同步的概念

​ 有两个线程,线程A写入,线程B读取线程A写入的值;线程A先写入,线程B才能读取;线程A和B之间就是一种同步关系。

18.2.7 互斥锁

保证多个线程访问共享数据不会出现数据错误问题:保证同一时刻只能有一个线程去操作

threading模块里面定义了Lock这个函数,通过调用这个函数可以获取到一把互斥锁

互斥锁的作用

  1. 保证同一时刻只有一个线程去操作共享数据,不会出现错误问题。
  2. 使用互斥锁会影响代码的执行效率

如果互斥锁使用不当,就会出现死锁的状态。

acquire() 加锁

release() 解锁

加锁和解锁必须成对出现

from threading import Thread,Lock

a = 0
n = 1000000
# 循环b次给全局变量a加1

# 创建互斥锁
lock = Lock()

def add():
    lock.acquire() #加锁
    for i in range(n):
        global a  #global 声明全局变量
        a += 1
    print("第一次",a)
    lock.release() #解锁

def add2():
    lock.acquire() #加锁
    for i in range(n):
        global a  #global 声明全局变量
        a += 1
    print("第二次",a)
    lock.release() #解锁


if __name__ == "__main__":
    # 创建两个子线程
    first = Thread(target=add)
    second = Thread(target=add2)

    # 启动线程
    first.start()
    second.start()

18.3 进程

18.2.1 进程介绍

运行一个程序就会有一个进程,一个进程默认有一个线程

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

进程的状态

  1. 就绪态:万事俱备,只欠cpu
  2. 执行态: cpu正在执行其功能
  3. 等待态:等待某些条件满足
import time
print("我们在学习进程")
name = input("请输入你的名字") #用户输入,进行阻塞,等待态。
print(name)    #运行状态
time.sleep(2)   #睡眠2秒,阻塞状态
print("对酒当歌,人生几何") #运行状态

18.3.2 进程创建

multiprocessing模块就是跨平台版本的多进程模块,提供了一个Process类来代表一个进程对象,这个对象可以理解为是一个独立的进程,可以执行另外的事情。


from multiprocessing import Process

# Process 类  参数
#    target : 调用对象,子进程要执行的任务
#    args:以元组的形式传值
#    kwargs:以字典的形式传值


# 常用的方法:
#    start() 开启子进程
#    is_alive()  判断子进程是否还活着,存活为True
#    join   主进程等待子进程执行完

# 常用的属性
#    name  当前进程的别名
#    pid   当前进程的进程号

import os
from multiprocessing import Process

def one():
    print("这是子进程一")
    print(f"子进程id{os.getpid()},父进程id{os.getppid()}")

def two():
    print("这是子进程二")
    print(f"子进程id{os.getpid()},父进程id{os.getppid()}")

if __name__ == "__main__":
    # 创建子进程
    p1 = Process(target=one,name="进程名称1")
    p2 = Process(target=two)

    #开启
    p1.start()
    p2.start()

    print("p1的子进程名是:",p1.name)
    print("p2的子进程名是: ",p2.name)

    #查看子进程的进程号
    print(p1.pid)
    print(p2.pid)

    print(f"主进程{os.getpid()},父进程:{os.getppid()}")
    # 在cmd中,输入tasklist,找到pycharm.ext就可以看到进程号

is_alive() 和 join()


def speak(name):
    print(f"现在{name}在说话")

def listen(name2):
    print(f"{name2}在听课")

if __name__ == "__main__":

    p1 = Process(target=speak, args=('九歌',))
    p2 = Process(target=listen, args=('李四',))

    p1.start()
    p1.join() #等待p1执行完,再执行后面的操作
    p2.start()


    print("p1的状态是:",p1.is_alive())
    print("p2的状态是:",p2.is_alive())



18.3.3 进程的通信

进程间不共享全局变量

import time
li = []
# 写入数据
def wdata():
    for i in range(5):
        li.append(i)
        time.sleep(0.2)
    print("写入的数据是",li)

# 读取数据
def rdata():
    print("读取的数据是:",li)

if __main__ == "__main__":
    p1 = Process(target=wdata)
    p2 = Process(target=rdata)

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

进程的通信保证资源的传输

可以使用multiprocessing模块的Queue实现多进程之间的数据传输,Queue本身是一个消息队列程序

q.put() 放入数据

q.get() 取出数据


# 入队
q.put() #放入数据

# 出队
q.get() #取出数据

# 导入模块
from queue import Queue
# 初始化一个队列对象
q = Queue(3) #3表示最多可以接受3条消息
q.put('我今天去输液了,输的什么液,想你的夜')
q.put("你都不知道心疼人的")
q.put("被一个人牵动着情绪很烦,但也可以很甜蜜")

# get() #取出
print(q.get())
print(q.get())
print(q.get())


# q.empty() 判断队列是否为空,为空返回True,否则为False
# q.qsize() 队列中的数量
# q.full()  判断队列是否满了,满了返回True

print("目前的消息数量:",q.qsize())

# 通过队列来传递消息
from multiprocessing import Process,Queue
import time

li = ["蒸羊羔","蒸熊掌","蒸花鸭"]

# 写入数据
def wdata(q): #q表示队列对象
    for i in range(3):
        print(f"将早餐{i}放进去")
        q.put(i)
        time.sleep(0.2)

# 读取数据
def rdata(q): #q表示队列对象
    # 只要还有消息,就一直取出来
    while True:
        if q.empty(): #判断队列是否为空
           break  #跳出循环
        else:
            print("顾客从队列中获取到:",q.get())



if __name__ == "__main__":
    # 创建队列对象
    q =Queue()  #省略里面的参数,没有大小限制
    p1 = Process(target=wdata,args=(q,))
    p2 = Process(target=rdata,args=(q,))

    p1.start()
    p2.start()

18.3.4 进程池

把子进程放到进程池之中

进程池的概念

​ 定义一个池子,在里面放上固定数量的进程,有需求,就拿一个池子中的进程来处理任务

​ 处理完毕,进程并不关闭,而是将这个进程再放回池子中继续等待任务

方法

p.apply_async() 异步非阻塞,不用等待当前进程执行,随时根据系统调度来进行进程切换。

p.close() 关闭进程池

p.join() 主进程阻塞,等待所有工作进程退出,只能再close()后调用

from multiprocessing import Pool
import time

def work(a):
    print("我们在上课")
    time.sleep(2)
    return a * 3

if __name__ == "__main__":
    #定义一个进程池,最大进程数3
    p = Pool(3)

    li = []
    for i in range(6):
        #p.apply_async(调用的目标,传递的参数)
        res = p.apply_async(work,args=(i,)) #异步运行
        # 异步:进程不需要一直等待下去,而是继续执行下面的操作,不管其他进程的状态

        li.append(res) #将结果进行保存
        print(res.get()) #打印结果

    # 关闭进程池
    p.close()
    # 等待p进程池中所有子进程执行完,必须放在close方法后面
    p.join()

    # 使用get来获取apply_async的结果
    for i in li:
        print(i.get())

18.4 协程

18.4.1 协程介绍

协程,又称为微线程,纤程。英文名Coroutine

协程是python中另外一个实现多任务的方式,只不过比线程占用更小执行单元(理解为需要的资源)。为啥说它是一个执行单元,因为它自带CPU上下文。这样只有在合适的时机,我们可以把一个协程切换到另一个协程。

只要这个过程中保存或恢复CPU上下文那么程序还是可以运行的。

协程:单线程下的并发,又称为微线程

对于协程来说,程序员就是上帝,你想让他执行到哪里,它就执行到哪里。

使用场景

  1. 如果一个线程里面io操作比较多,协程就比较适用
  2. 适合用于高并发处理

简单实现协程

import time

def work1():
    while True:
        yield '心疼哥哥'

def work2():
    while True:
        yield '互联网水太深,你把握不住'

if __name__ == "__main__":

    w1 = work1()
    w2 = work2()

    while True:
        print(next(w1))
        print(next(w2))
# 程序员可以简单的通过代码,控制w1和w2的执行顺序

18.4.2 greenlet

greenlet: 是一个用C实现的协程模块,通过设置switch()来实现任意函数之间的切换

属于手动切换,当遇到IO操作,程序会阻塞,而不能进行自动切换

安装命令 pip install greenlet

卸载命令 pip uninstall ungreenlet

查看已安装模块的命令 pip list


from greenlet import greenlet
def eat():
    print("开始吃夜宵")
    g2.switch() #切换到g2中运行,后面就不执行了
    print("吃饱了")

def study():
    print("开始学习")
    print("学习完毕")


# 实例化一个协程对象
# greenlet(任务名)
g1 = greenlet(eat)
g2 = greenlet(study)

g1.switch() # 切换到g1中运行


# 输出结果
# 开始吃夜宵
# 开始学习
# 学习完毕

18.4.3 gevent

greenlet是手动切换的,比较麻烦

gevent 则是自动切换的

gevent 遇到IO操作,会进行自动切换,属于主动式切换。

在gevent中用到的主要模式是greenlet

pip install gevent

import gevent

# 创建协程对象
# gevent.spawn(函数名) 
# join 阻塞,等待某个协程执行完毕
# joinall 参数是一个协程对象列表,会等待所有的协程都执行完毕再退出

执行A/B两个任务,当A、B遇到耗时操作,gevent会让A继续执行,同时也开始执行B任务

A完成了耗时操作后,B在对应的时间也完成了耗时操作

# 切记,py文件不要跟第三方模块、内置模块重名

import gevent
def write():
    print("我们在写写成代码")
    gevent.sleep(1)   #等待的同时,开启其他所有协程,让所有协程(g1,g2)并发执行
    print("终于写完了")

def listen():
    print("现在先好好听课把")
    gevent.sleep(1) 
    print("课间休息")

g1 = gevent.spawn(write)
g2 = gevent.spawn(listen)


g1.join() #等待g1对象执行结束
g2.join() #等待g2对象执行结束


# 运行结果
# 我们在写写成代码
# 现在先好好听课把
# 终于写完了
# 课间休息

joinall 需要等待所有的协程对象执行完,再退出


def work(name):
    for i in range(3):
        gevent.sleep(1) #等待的同时,开启其他所有协程,让所有协程并发执行
        print(f'函数名是:{name},i的值是:{i}')

gevent.joinall([
    gevent.spawn(work,'小白'),
    gevent.spawn(work,'小鹅'),

])

18.4.4 打补丁

给程序打补丁

monkey补丁,

猴子补丁的功能:

  1. 拥有在模块中替换的功能
from gevent import monkey
import gevent
import time


monkey.patch_all() # 将time.sleep()代码,替换成gevent.sleep()代码 。 必须写在最前面

def work(name):
    for i in range(3):
        # 将用到的耗时操作的代码,替换为gevent里面自己实现耗时操作的代码
        time.sleep(1)
        print(f'函数名是:{name},i的值是:{i}')

gevent.joinall([
    gevent.spawn(work,'小白'),
    gevent.spawn(work,'小鹅'),

])

18.4.5 综合例子

import gevent

def funa():
    print("wsc:今天有事情,跟孙yn打个电话")  #1
    gevent.sleep(2)
    print("wsc: 怎么突然挂电话了,再打过去...") #5

def funb():
    print("孙yn:wsc来电话啦。。。") #2
    gevent.sleep(3)
    print("孙yn: 他又打电话了")   #6

def func():
    print("亲爱的,你在干啥。。。") #3
    gevent.sleep(1)
    print("你过来呀")     #4

gevent.joinall([
    gevent.spawn(funa),
    gevent.spawn(funb),
    gevent.spawn(func)
])

# 输出结果
wsc:今天有事情,跟孙yn打个电话
孙yn:wsc来电话啦。。。
亲爱的,你在干啥。。。
你过来呀
wsc: 怎么突然挂电话了,再打过去...
孙yn: 他又打电话了

总结

  1. 进程是资源分配的基本单位,线程是CPU调度的基本单位
  2. 对比:
    1. 进程:切换需要的资源最大,效率比较低
    2. 线程:切换需要的资源一般,效率一般
    3. 协程:切换需要的资源很小,效率较高
  3. 多线程:适合IO密集型操作(读写数据比较多,比如爬虫)
  4. 多进程:适合cpu密集型操作(科学计算,计算圆周率,对视频进行高清解码)
  5. 一个运行的程序至少有一个进程,一个进程至少有一个线程。
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
DevOpSec DevOpSec
3年前
python多线程原理和详解(一)
python多线程原理和详解线程概念1.线程是什么?线程也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。
Wesley13 Wesley13
3年前
java线程笔记
线程个人理解:线程是一种运行单元,是进程内的拆分(就像一个房子,里面可以有电视在播放、人在吃饭),不同的线程可以同时干不同的事情。其他:进程:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。协程:协程是比线程的更小的一种程序的拆分,和
Wesley13 Wesley13
3年前
4、jstack查看线程栈信息
1、介绍利用jps、top、jstack命令找到进程中耗时最大的线程,以及线程状态等等,同时最后还可以显示出死锁的线程查找:FoundoneJavaleveldeadlock即可1、jps获得进程号!(https://oscimg.oschina.net/oscnet/da00a309fa6
Stella981 Stella981
3年前
Gevent简明教程
1、前述进程线程协程异步并发编程(不是并行)目前有四种方式:多进程、多线程、协程和异步。多进程编程在python中有类似C的os.fork,更高层封装的有multiprocessing标准库多线程编程python中有Thread和threading异步编程在linux下主要有三种实现selec
Stella981 Stella981
3年前
Goroutine(协程)为何能处理大并发?
简单来说:协程十分轻量,可以在一个进程中执行有数以十万计的协程,依旧保持高性能。进程、线程、协程的关系和区别:进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程是的)。协程和线程一样共享堆
Stella981 Stella981
3年前
Python进程、线程、协程的对比
1\.执行过程每个线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在进程中,由进程提供多个线程执行控制。每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。协程,又称微线程,Coroutine。执行过程中,在子程序内部可中断,然后转而
Stella981 Stella981
3年前
Noark入门之线程模型
0x00单线程多进程单线程与单进程多线程的目的都是想尽可能的利用CPU,减少CPU的空闲时间,特别是多核环境,今天咱不做深度解读,跳过...0x01线程池锁最早的一部分游戏服务器是采用线程池的方式来处理玩家的业务请求,以达最大限度的利用多核优势来提高处理业务能力。但线程池同时也带来了并发问题,为了解决同一玩家多个业务请求不被
Wesley13 Wesley13
3年前
Java面试问题——线程全面详解总结
一、多线程是什么?为什么要用多线程?介绍多线程之前要介绍线程,介绍线程则离不开进程。首先进程 :是一个正在执行中的程序,每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元;线程:就是进程中的一个独立控制单元,线程在控制着进程的执行。一个进程中至少有一个进程。多线程:一个进程中不只有一
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这