Python-多线程

一、进程与线程

一个进程必须有一个线程

1.全局解释器锁

GIL的全称是Global Interpreter Lock(全局解释器锁),Python最初的设计理念在于,为了解决多线程之间数据完整性和状态同步的问题,设计为在任意时刻只能由一个线程在解释器中运行。因此Python中的多线程是表面上的多线程(同一时刻只有一个线程),不是真正的多线程

真正意义上的多线程是由CPU来控制的,Python中的多线程是由GIL控制的。如果一个CPU密集型的程序,用C语言写,运行在一个四核处理器上,采用多线程的话最多可以获得4倍的效率提升。但是用Python写的话,效率不会提高,甚至会变慢,因为除了程序本身执行外,还多了线程切换所花的时间。

因此,Python多线程相对更适合写I/O密集型的程序,真正对效率要求高的CPU密集型程序都用C/C++去写。

2.并行与并发


二、代码

threading.Thread

# 我们使用threading模块中的Thread类来创建线程对象
# threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)`
*   group:默认为None(该参数是为了以后实现ThreadGroup类而保留的)
*   target:在run方法中调用的可调用对象,即需要开启线程的可调用对象,比如函数或方法。
*   name:线程名称,默认为“Thread-N”形式的名称,N为较小的十进制数。
*   args:在参数target中传入的可调用对象的参数元组,默认为空元组()。
*   kwargs:在参数target中传入的可调用对象的关键字参数字典,默认为空字典{}。
*   daemon:默认为None,即继承当前调用者线程(即开启线程的线程,一般就是主线程)的守护模式属性,如果不为None,则无论该线程是否为守护模式,都会被设置为“守护模式”。

# 线程对象的一些重要方法
*   start():开启线程活动。它将使得run()方法在一个独立的控制线程中被调用,需要注意的是同一个线程对象的start()方法只能被调用一次,如果调用多次,则会报RuntimeError错误。
*   run():此方法代表线程活动。
*   join(timeout=None):让当前调用者线程(一般为主线程)等待,直到线程结束。
*   daemon:表示该线程是否为守护线程,True或者False。设置一个线程的daemon必须在线程的start()方法之前,否则会报RuntimeError错误。
*   这个值默认继承自创建它的线程,主线程默认是非守护线程,所以在主线程中创建的线程默认都是非守护线程的,即daemon=False

多线程-start

#!/usr/bin/python
# -*- coding:utf-8 -*-
"""
@Time: 2021-08-22 8:37
@Target: 多线程
@Author: dingkw
"""
from threading import Thread
from time import sleep


print('这是主线程1')


def task(urls):
    sleep(1)
    print(urls)


# 实例化线程对象
thread_1 = Thread(target=task, args=("http://www.baidu.com", ))
# 启动线程
thread_1.start()
print('这是主线程2,结束了')
# 当主线程执行完,子线程仍然继续执行
>>> 这是主线程1
>>> 这是主线程2,结束了
>>> http://www.baidu.c

多线程-daemon

#!/usr/bin/python
# -*- coding:utf-8 -*-
"""
@Time: 2021-08-22 8:37
@Target: 
@Author: dingkw
"""
from threading import Thread
from time import sleep


print('这是主线程1')


def task(urls):
    sleep(1)
    print(urls)


# 实例化线程对象
# 设置主线程守护方法1:daemon=True
thread_1 = Thread(target=task, args=("http://www.baidu.com", ),daemon=True)
# 启动线程
thread_1.start()
# 设置主线程守护方法2:thread_1.daemon()
thread_1.daemon()
print('这是主线程2,结束了')
# 当主线程执行结束,子线程不再执行
>>> 这是主线程1
>>> 这是主线程2,结

多线程-join

#!/usr/bin/python
# -*- coding:utf-8 -*-
"""
@Time: 2021-08-22 8:37
@Target: 
@Author: dingkw
"""
from threading import Thread
from time import sleep


print('这是主线程1')


def task(urls):
    sleep(1)
    print(urls)


# 实例化线程对象
thread_1 = Thread(target=task, args=("http://www.baidu.com", ),daemon=True)
# 启动线程
thread_1.start()
# 等待子线程结束,才会继续执行
thread_1.join()
print('这是主线程2,结束了')


>>> 这是主线程1
>>> http://www.baidu.com
>>> 这是主线程2,

线程池

简介

系统启动一个新线程的成本是很高的,因为它涉及与操作系统的交互。在这种情况下,使用线程池可以很好地提升性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池。

线程池在系统启动时即创建大量空闲的线程,程序只要将一个函数提交给线程池,线程池就会启动一个空闲的线程来执行它。当该函数执行结束后,该线程并不会死亡,而是再次返回到线程池中变成空闲状态,等待下一个函数。

此外,使用线程池可以有效地控制系统中并发线程的数量。当系统中包含有大量的并发线程时,会导致系统性能急剧下降,甚至导致Python解释器崩溃,而线程池的最大线程数参数可以控制系统中并发线程的数量不超过此数。

使用

在过去,我们可以使用第三方模块threadpool来创建线程池。

但是现在主流的使用线程池的模块是python3中自带的模块concurrent.futures模块中的ThreadPoolExecutor,

如果对threadpool感兴趣的小伙伴可以自行搜索相关的信息。

#!/usr/bin/python
# -*- coding:utf-8 -*-
"""
@Time: 2021-08-22 22:25
@Target: 线程池
@Author: dingkw
"""
import time
import random
from concurrent.futures import  ThreadPoolExecutor


def func(name):
    s = random.randint(1,5)
    print(f'当前线程是{name},休眠{s}秒')
    time.sleep(s)
    print(f'线程{name}结束')


if __name__ == '__main__':
    # 创建一个最大容纳数量为5的线程池对象t
    # 通过submit提交执行的函数到线程池中,
    # 当线程池中,某个线程完成会继续把线程6放入线程池
    # 直到所有线程执行完毕
    with ThreadPoolExecutor(max_workers=5) as t:
        for i in range(1,11):
            t.submit(func,i
posted @ 2021-09-12 21:11  难删亦删  阅读(711)  评论(0)    收藏  举报