线程是一种多任务编程的方式, 可以使用计算机多核资源. 线程又被称为轻量级的进程
线程特征
* 线程是计算机核心分配的最小单位
* 一个进程可以包含多个线程
* 线程也是一个运行过程, 也要消耗计算机资源. 多个线程共享其进程的资源和空间
* 线程也拥有自己特有的资源属性, 比如指令集, TID 等
* 线程无论创建还是删除还是运行资源消耗都小于进程
* 多个线程之间并行执行, 互不干扰
threading 线程模块
- from threading import Thread
- t = Thread(target, [, args], [kwargs])
创建线程对象
target 绑定线程函数
args 元组 给线程函数位置传参
kwargs 字典 给线程函数键值传参
t.start() 启动线程
t.join([timeout]) 回收线程
- import threading
- import os
- a = 1
- # 线程函数
- def music():
- print("进程 pid 号", os.getpid())
- global a
- print("a =",a)
- a = 10000
- t = threading.Thread(target=music) # 创建线程对象
- t.start() # 启动线程
- print("进程 pid 号", os.getpid())
- t.join() # 回收线程
- print("Main a:",a)
- # 进程 pid 号 12549
- # 进程 pid 号 12549
- # a = 1
- # Main a: 10000
os.getpid 获取的是进程的 pid 号, 线程是进程中的一个成员.
线程中改的变量, 是进程中的变量.并没有新开辟一个空间.
线程属性
t.is_alive() 查看线程状态
t.name 线程名称 默认 Thread-1
t.setName() 设置线程名称
threading.currentThread() 获取当前线程对象
- from threading import Thread,currentThread
- from time import sleep
- #线程函数
- def fun(sec):
- print("线程属性测试")
- sleep(sec)
- #获取线程对象 getName() 获取名字
- print("%s 线程结束"%currentThread().getName())
- thread = []
- for i in range(3):
- t = Thread(target = fun,name = "tedu%d"%i,\
- args = (3,))
- thread.append(t)
- t.start()
- print(t.is_alive()) #查看进程状态
- thread[1].setName('Tarena') #设置线程名称
- print(thread[2].name) #获取线程名称
- #回收线程
- for i in thread:
- i.join()
- # 线程属性测试
- # True
- # 线程属性测试
- # True
- # 线程属性测试
- # True
- # tedu2
- # Tarena 线程结束
- # tedu0 线程结束
- # tedu2 线程结束
- View Code
- t.daemon
默认情况下, 主线程的结束不会影响分支线程, 如果设置为 True 则主线程退出分支线程也会退出
设置方法:
- t.daemon = True
- t.setDaemon()
线程 daemon 属性的设置在 start 前; 一般设置 daemon 后不会使用 join
- from threading import Thread
- from time import sleep
- def fun():
- sleep(3)
- print("线程属性测试")
- t = Thread(target=fun, name = "Tarena")
- # 主线程退出分支线程也退出
- t.setDaemon(True)
- t.start()
- t.setName("Tedu")
- print("Name:",t.getName()) # 线程名称
- print("Alive:",t.is_alive()) # 线程生命周期
- print("is Daemon",t.isDaemon()) # 主进程随着分支进程退出
自定义线程类
继承 Thread 类
运行 Thread 类中的__init__方法以获取父类属性
重写 run 方法
使用方法
实例化对象
调用 start 自动化执行 run 方法
调用 join 回收线程
- from threading import Thread
- class ThreadClass(Thread):
- # 重写父类 init
- def __init__(self, *args, **kwargs):
- self.attr = args[0]
- super().__init__() # 加载父类 init
- def fun1(self):
- print("函数1")
- def fun2(self):
- print("函数2")
- # 重写 run, 逻辑调用
- def run(self):
- self.fun1()
- self.fun2()
- t = ThreadClass("abc")
- t.start()
- t.join()
- # 函数1
- # 函数2
同步互斥
线程间通信方法
1.通信方法: 线程间使用全局变量进行通信
2. 共享资源争夺
共享资源: 多个进程或者线程都可以操作的资源称为共享资源. 对共享资源的操作代码段称为临界区.
影响 : 对共享资源的无序操作可能会带来数据的混乱, 或者操作错误. 此时往往需要同步互斥机制协调操作顺序.
3. 同步互斥机制
同步 : 同步是一种协作关系, 为完成操作, 多进程或者线程间形成一种协调, 按照必要的步骤有序执行操作.
互斥 : 互斥是一种制约关系, 当一个进程或者线程占有资源时会进行加锁处理, 此时其他进程线程就无法操作该资源, 直到解锁后才能操作.
线程同步互斥方法
线程 Event
from threading import Event
e = Event() 创建线程 event 对象
e.wait([timeout]) 阻塞等待 e 被 set
e.set() 设置 e, 使 wait 结束阻塞
e.clear() 使 e 回到未被设置状态
e.is_set() 查看当前 e 是否被设置
- from threading import Thread,Event
- s = None # 用于通信
- e = Event() # 创建 event 对象
def 杨子荣 ():
- print("杨子荣前来拜山头")
- global s
- s = "天王盖地虎"
- e.set() # 对 e 设置
- t = Thread(target = 杨子荣)
- t.start()
- print("说对口令就是自己人")
- e.wait() # 阻塞等待口令说出
- if s == '天王盖地虎':
- print("宝塔镇河妖")
- print("确认过眼神, 你是对的人")
- else:
- print("打死他...")
- t.join()
线程锁 Lock
from threading import Lock
lock = Lock() 创建锁对象
lock.acquire() 上锁 如果 lock 已经上锁再调用会阻塞
lock.release() 解锁
- with lock: # 上锁
- ...
- ...
with 代码块结束自动解锁
- from threading import Thread,Lock
- a = b = 0
- lock = Lock() # 定义锁
- def value():
- while True:
- lock.acquire() # 上锁
- if a != b:
- print("a = %d,b = %d"%(a,b))
- lock.release() # 解锁
- t = Thread(target = value)
- t.start()
- while True: # 上锁
- with lock:
- a += 1
- b += 1
- # 自动解锁
- t.join()
python 线程的 GIL 问题
GIL (全局解释器锁)
python ---》 支持线程操作 ---》IO 的同步和互斥 --》 加锁 ----》 超级锁, 给解释器加锁
后果: 一个解释器, 同一时刻只解释一个线程, 此时其他线程需要等待. 大大降低了 python 线程的执行效率
python GIL 问题解决方案
* 修改 c 解释器
* 尽量使用多进程进行并行操作
* python 线程可以用在高延迟多阻塞的 IO 情形
* 不使用 cpython c# java 做解释器
效率测试
分别测试 多进程 多线程 单进程执行相同的 IO 操作和 CPU
- # 计算密集
- def count(x,y):
- c = 0
- while c < 7000000:
- x += 1
- y += 1
- c += 1
- #io 密集
- def write():
- f = open("test.txt",'w')
- for x in range(2000000):
- f.write("hello world\n")
- f.close()
- def read():
- f = open("test.txt")
- lines = f.readlines()
- f.close()
- View Code
操作的时间
- # 单进程程序
- from test import *
- import time
- # t = time.time()
- # for i in range(10):
- # count(1,1)
- # print("Line cpu:",time.time() - t)
- t = time.time()
- for i in range(10):
- write()
- read()
- print("Line IO:",time.time() - t)
- View Code
- Line CPU: 8.15166711807251
- Line IO: 6.841825246810913
- from test import *
- import threading
- import time
- counts = []
- t = time.time()
- for x in range(10):
- th = threading.Thread(target = count,args = (1,1))
- th.start()
- counts.append(th)
- for i in counts:
- i.join()
- print("Thread cpu",time.time() - t)
- View Code
- from test import *
- import threading
- import time
- counts = []
- def io():
- write()
- read()
- t = time.time()
- for x in range(10):
- th = threading.Thread(target = io)
- th.start()
- counts.append(th)
- for i in counts:
- i.join()
- print("Thread IO",time.time() - t)
- View Code
- Thread CPU 8.414522647857666
- Thread IO 6.023292541503906
- from test import *
- import multiprocessing
- import time
- counts = []
- t = time.time()
- for x in range(10):
- th = multiprocessing.Process\
- (target = count,args = (1,1))
- th.start()
- counts.append(th)
- for i in counts:
- i.join()
- print("Process cpu",time.time() - t)
- View Code
- from test import *
- import multiprocessing
- import time
- counts = []
- def io():
- write()
- read()
- t = time.time()
- for x in range(10):
- th = multiprocessing.Process(target = io)
- th.start()
- counts.append(th)
- for i in counts:
- i.join()
- print("Process IO",time.time() - t)
- View Code
- Process CPU 4.079084157943726
- Process IO 3.2132551670074463
进程和线程的区别和联系
两者都是多任务编程的方式, 都能够使用计算机的多核
进程的创建删除要比线程消耗更多的计算机资源
进程空间独立, 数据安全性好, 有专门的进程间通信方法
线程使用全局变量通信, 更加简单, 但是需要同步互斥操 作
一个进程可以包含多个线程, 线程共享进程的空间资源
进程线程都独立执行, 有自己的特有资源如属性, id, 命令集等
使用情况:
一个进程中并发任务比较多, 比较简单, 适合使用多线程
如果数据程序比较复杂, 特别是可能多个任务通信比较多 的时候, 要考虑到使用线程同步互斥的复杂性
多个任务存在明显差异, 和功能分离的时候没有必要一定 写入到一个进程中
使用 python 考虑线程 GIL 问题
来源: https://www.cnblogs.com/LXP-Never/p/9474024.html