什么是线程
线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
Python中线程的问题
在Python3中实现的大部分运行任务里,不同的线程实际上并没有同时运行:"它们只是看起来像是同时运行的。"
1.大家很容易认为线程化是在程序上运行两个(或多个)不同的处理器,每个处理器同时执行一个独立的任务。
这种理解并不完全正确,线程可能会在不同的处理器上运行,但一次只能运行一个线程。
2.同时执行多个任务需要使用非标准的Python运行方式:用不同的语言编写一部分代码,
或者使用多进程模块multiprocessing,但这么做会带来一些额外的开销。
3.由于Python默认的运行环境是CPython(C语言开发的Python),所以线程化可能不会提升所有任务的运行速度。
这是因为和GIL(Global Interpreter Lock)的交互形成了限制:一次只能运行一个Python线程。
4.线程化的一般替代方法是:
- 让各项任务花费大量时间等待外部事件。
- 但问题是,如果想缩短等待时间,会需要大量的CPU计算,结果是程序的运行速度可能并不会提升。
5.当代码是用Python语言编写并在默认执行环境CPython上运行时,会出现这种情况。
如果线程代码是用C语言写的,那它们就能够释放GIL并同时运行。
6.如果只用Python语言在默认的Python执行环境下运行,并且遇到CPU受限的问题,
那就应该用"多进程模块multiprocessing"来解决。
在程序中使用线程也可以简化设计。本文中的大部分示例并不保证可以提升程序运行速度,其目的是使设计结构更加清晰、便于逻辑推理。
threading线程常用方法
python标准库-threading
1.实例化thread类
t = threading.Thread(group=None, target=None, name=None,
args=(), kwargs=None, *, daemon=None)
参数解析:
group:线程组,貌似没什么用
target:调用的方法名,记得不加()
name:线程开启后,该线程的名字
args:参数,元组的形式传入
kwargs:参数
daemon:设置是否为守护线程
2.以列表形式返回当前所有存活的Thread对象
threading.enumerate()
3.返回当前存活的线程类 Thread 对象。返回的计数等于 enumerate() 返回的列表长度。
threading.active_count()
4.阻塞函数,timeout 允许的最大值。传入超过这个值的 timeout 会抛出 OverflowError 异常。
threading.TIMEOUT_MAX
5.start()
开始线程活动。
6.run()
代表线程活动的方法。
7.join(timeout=None)
等待,直到所有线程终结。
8.is_alive()
返回线程是否存活。
9.daemon
一个表示这个线程是(True)否(False)守护线程的布尔值。
一定要在调用 start() 前设置好,不然会抛出 RuntimeError 。
初始值继承于创建线程;主线程不是守护线程,因此主线程创建的所有线程默认都是 daemon = False。
当没有存活的非守护线程时,整个Python程序才会退出。
开启线程的两种方法
# Thread是线程类,有两种使用方法,直接传入要运行的方法或从Thread继承并覆盖run()
# 当调用Thread的时候,不会创建线程。
# 当调用Thread创建出来的实例对象的start方法的时候,才会创建线程以及开始运行这个线程。
一.直接传入要运行的方法
# coding:utf8
import threading
import time
def dog(num):
print("汪 汪 汪 - %s" %num)
time.sleep(1)
if __name__ == '__main__':
# 1.普通模式启动
t1_start=time.time()
for num in range(5):
dog(num)
t2_end=time.time()
print("普通模式耗时:%s" %(t2_end-t1_start))
# 2.线程模式
t1_start=time.time()
for num in range(5):
t1 = threading.Thread(target=dog,args=(num,))
t1.start()
t2_end=time.time()
print("线程模式耗时:%s" %(t2_end-t1_start))
-------------------------结果--------------------------------------
汪 汪 汪 - 0
汪 汪 汪 - 1
汪 汪 汪 - 2
汪 汪 汪 - 3
汪 汪 汪 - 4
普通模式耗时:5.004029750823975
汪 汪 汪 - 0
汪 汪 汪 - 1
汪 汪 汪 - 2
汪 汪 汪 - 3
汪 汪 汪 - 4
线程模式耗时:0.003961086273193359
------------------------------------------------------------------
二.从Thread继承并覆盖run()
import threading
import time
class A(threading.Thread):
def __init__(self):
super().__init__()
#这里一定是run方法,不能是其他的
def run(self):
for i in range(5):
print(i,end='')
if __name__ == "__main__":
t = A()
t.start()
------------------------------------------------------------------
0 1 2 3 4
线程之间的通信-锁
如果多个线程对某一资源同时进行修改,可能会存在不可预知的情况。
为了修改数据的正确性,需要把这个资源锁住,只允许线程依次排队进去获取这个资源。
当线程A操作完后,释放锁,线程B才能进入。
创建锁
mutex = threading.Lock()
锁定
mutex.acquire()
解锁
mutex.release()
# coding:utf8
import threading,time
#创建锁
lock=threading.Lock()
num=0
def dog(nums):
global num
#加锁
lock.acquire()
for i in range(nums):
num+=1
#解锁
lock.release()
print("汪 汪 汪 - %s " %num)
def cat(nums):
global num
lock.acquire()
for i in range(nums):
num+=1
lock.release()
print("miao miao miao - %s " % num)
if __name__ == '__main__':
t1=threading.Thread(target=dog,args=(1000000,))
t2=threading.Thread(target=cat,args=(1000000,))
t1.start()
t2.start()
time.sleep(2)
print('main',num)
-------------结果-----------------
汪 汪 汪 - 1000000
miao miao miao - 2000000
main 2000000
注:如果不加锁,结果会不真实