python 多线程与线程池

本文详细介绍了Python中的线程概念,包括线程的定义、并发与并行的区别、同步与异步的概念,并通过实例展示了如何使用Threading模块创建和管理线程。还讨论了线程间的资源竞争问题,提出了互斥锁和线程池作为解决方案。此外,文章还探讨了全局解释器锁(GIL)对Python多线程的影响。
摘要由CSDN通过智能技术生成

线程:
线程(Thread)也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行

并发与并行:
单核CUP执行多任务,由于CUP执行代码都是顺序执行的,当执行多个任务的时候操作系统会轮流让各个任务交替执行,由于执行速度过快,感觉是所有的任务同时执行一样,称为并发,当任务数小于CUP数量时,这是任务是真正的在一起运行,称为并行。

同步与异步:
同步(协同步调):线程在访问某 一资源时,获得了资源的返回结果后才会执行其他操作。
异步(步调各异):与同步相对,在访问某一资源时,无论资源是否有返回结果,都会进行下一步操作,当资源有结果时,系统会自动通知线程。

举例:创建两个普通函数

import time

def fun():
    for i in range(3):
        time.sleep(1)
        print('fun:', i)


def fun1():
    for i in range(3):
        time.sleep(1)
        print('fun1:', i)

st = time.time()
fun()
fun1()
et = time.time()
print("运行时间:", et - st)


运行结果:
fun: 0
fun: 1
fun: 2
fun1: 0
fun1: 1
fun1: 2
运行时间: 6.063438653945923

Threading模块
通过多线程对多任务的处理,通过threading中的Thread模块。如下通过Thread创建一个线程对象,将需要运行的函数名t赋值给target,调用对象的start方法来启动线程。

在这里插入图片描述

import time
from threading import Thread


def fun():
    for i in range(3):
        time.sleep(1)
        print('fun:', i)


def fun1():
    for i in range(3):
        time.sleep(1)
        print('fun1:', i)


st = time.time()
fun = Thread(target=fun) # 创建线程对象
fun.start() # 启动线程
fun1()
et = time.time()
print("运行时间:", et - st)
结果:
fun: 0
fun1: 0
fun1: 1
fun: 1
fun: 2
fun1: 2
运行时间: 3.0180318355560303

如果fun1也通过创建线程的方式来启动,来看下效果:

import time
from threading import Thread


def fun():
    for i in range(3):
        time.sleep(1)
        print('fun:', i)


def fun1():
    for i in range(3):
        time.sleep(1)
        print('fun1:', i)


st = time.time()
fun = Thread(target=fun) # 创建线程对象
fun1 = Thread(target=fun1) # 创建线程对象
fun.start() # 启动线程
fun1.start() # 启动线程
et = time.time()
print("运行时间:", et - st) # 主线程

结果:
运行时间: 0.0009970664978027344
fun: 0
fun: 1
fun: 2
fun1: 0
fun1: 1
fun1: 2

这时候主线程并print并没有等待子线程运行结束,现有需求需要等待子线程执行结束后才运行主线程,可以用join()方法来控制等待子线程运行的时间,默认是等待子线程运行结束。
举例:

import time
from threading import Thread


def fun():
    for i in range(3):
        time.sleep(1)
        print('fun:', i)


def fun1():
    for i in range(3):
        time.sleep(1)
        print('fun1:', i)


st = time.time()
fun = Thread(target=fun) # 创建线程对象
fun1 = Thread(target=fun1) # 创建线程对象

fun.start() # 启动线程
fun1.start() # 启动线程

fun.join() # 等待fun线程运行结束,fun.join(2) 等待2秒
et = time.time()
print("运行时间:", et-st)

结果:
fun: 0
fun1: 0
fun1: 1
fun: 1
fun: 2
fun1: 2
运行时间: 3.0180318355560303

任务函数有参数时
方式一:元组的形式赋值给args

import time
from threading import Thread

def fun(name,age):
    for i in range(3):
        time.sleep(1)
        print(f'fun:{name},{age}', i)


fun = Thread(target=fun, args=('MING',19))  # 创建线程对象,函数名赋值给args
fun.start()

结果:
fun:MING,19 0
fun:MING,19 1
fun:MING,19 2

方式二:字典的方式赋值给kwargs

import time
from threading import Thread

def fun(name,age):
    for i in range(3):
        time.sleep(1)
        print(f'fun:{name},{age}', i)


fun = Thread(target=fun, kwargs={'name': 'MING', 'age': 19})  # 创建线程对象
fun.start()

结果:
fun:MING,19 0
fun:MING,19 1
fun:MING,19 2

自定义线程类
重写线程类的run方法,在run方法中写线程执行的具体代码,调用start方法时会默认执行run下面的代码。

import time
from threading import Thread


class MyThread(Thread):
    def __init__(self,name):
        super().__init__() # 重写init方法
        self.name = name


    def run(self):
        for i in range(10):
            time.sleep(1)
            print(f'{self.name}请求第{i}次')


if __name__ == '__main__':
    for i in range(5):
        t = MyThread("MING")
        t.start()

多线程共享全局变量

from threading import Thread

a = 0

def fun():
    global a
    for i in range(100):
        a += 1
    print("fun:", a)


def fun1():
    global a
    for i in range(100):
        a += 1
    print("fun1:", a)


if __name__ == '__main__':
    Thread(target=fun).start()
    Thread(target=fun1).start()
    
>>> fun: 100
>>> fun1: 200

多线程是无法利用多核CPU的资源,在同一时间,只能有一个线程在执行,因为python解释器有把GIL(Global Interpreter Lock)全局解释器锁。

如图:当线程想要执行,必须先拿到GIL,可以把GIL看作是“通行证”,并且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行。GIL只在cpython中才有,因为cpython调用的是c语言的原生线程,所以他不能直接操作cpu,只能利用GIL保证同一时间只能有一个线程拿到数据。在这里插入图片描述
而当共享全局变量的时候则会出现新的问题,python线程执行的切换机制:
1.线程执行遇到IO耗时操作,比如sleep,文件的输入输出,网络请求等。
2.线程执行时间达到一个阈值后
当启动多个线程同时,引用的全局变量会造成资源竞争而导致数据不准确
如下:

from threading import Thread

a = 0


def fun():
    global a
    for i in range(100000):
        a += 1
    print("fun:", a)


def fun1():
    global a
    for i in range(100000):
        a += 1
    print("fun1:", a)


if __name__ == '__main__':
    Thread(target=fun).start()
    Thread(target=fun1).start()
    print('a:',a)
    
>>> fun:100000
>>> fun1:200000
>>> a: 165424

互斥锁:Lock类
而解决上面的问题可以通过引入互斥锁,状态为锁定/非锁定,当某个线程更改共享数据时,先将其锁定,此时资源的状态为 “锁定” ,其他线程不能更改直到该线程释放资源,将资源的状态变为 ”非锁定“ , 其他线程才能再次锁定该资源。

l = threading.Lock() #创建锁
l.acquire() # 锁定
l.release() # 释放

举例:

from threading import Thread, Lock

a = 0
lock = Lock()  # 创建锁


def fun():
    global a
    for i in range(100000):
        lock.acquire() # 上锁
        a += 1
        lock.release() # 释放锁
    print("fun:", a)


def fun1():
    global a
    for i in range(100000):
        lock.acquire() # 上锁
        a += 1
        lock.release() # 释放锁
    print("fun1:", a)


if __name__ == '__main__':
    t = Thread(target=fun)
    t.start()
    t1 = Thread(target=fun1)
    t1.start()
    t.join()
    t1.join()
    print('a:',a)

>>> fun:154229
>>> fun1:165424
>>> a: 200000

线程池:ThreadPoolExecutor
通过ThreadPoolExecutor 创建线程池,submit提交任务到线程池。max_workers指定线程池中的数量

import time
from concurrent.futures import ThreadPoolExecutor


def work(name):
    for i in range(5):
        time.sleep(1)
        print(f"{name}----{i}----")


with ThreadPoolExecutor(max_workers=4) as tp: # 创建线程池
    tp.submit(work, 'MING') # 往线程提交任务
    tp.submit(work, 'HOUX')
	tp.shutdown() # 等待线程池中所有的任务执行完成
    print("---主线程---")
    
运行结果:    
 
MING----0----HOUX----0----

MING----1----HOUX----1----

HOUX----2----MING----2----

MING----3----HOUX----3----

HOUX----4----MING----4----

---主线程---
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

久醉绕心弦,

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值