线程
概述
通俗点讲,在共享的空间里同时做不同的任务
进程和线程的区别
- 同一个进程中的所有线程共享同一内存空间,但是2个进程之间内存空间是独立的。
- 同一个进程中的所有线程的数据是共享的,多个进程之间的数据是独立的。
- 进程安全性高,线程安全性低
- 进程是资源分配的单元,线程是调度单元
python多线程编程
多线程修改全局变量
import time
from threading import Thread
"""多线程实现修改全局变量"""
# 定义一个全局变量
num = 0
print("num=", num)
def modify1():
global num
num += 1
print('修改一次num')
def modify2():
global num
num += 1
print('修改一次num')
def read():
print("读出线程,num的值是", num)
# 创建一个线程
t1 = Thread(target=modify1)
t2 = Thread(target=modify2)
t3 = Thread(target=read)
# 启动线程
t1.start()
t2.start()
t3.start()
# 睡眠一秒,防止线程未执行完就输出num
time.sleep(1)
print("主线程,num的值是", num)
类继承
from threading import Thread
"""简单多线程类继承"""
# 创建一个自己的多线程继承类
class MyThread(Thread):
def __init__(self, name):
# 调用父类的构造方法
Thread.__init__(self)
# super(MyThread, self).__init__()
self.name = name
def run(self):
print("类继承方式,我是", self.name)
# 实体化出来一个对象
myt1 = MyThread("江小白")
# 启动线程,线程就去执行类里的run方法
myt1.start()
线程的同步和互斥
临界资源
多线程编程时主要考虑内容:全局变量和代码资源
全局变量访问临界区
from threading import Thread
global_variable = 0
# 线程1,写数据
def write_data1():
global global_variable
for x in range(1000000):
global_variable += 1
print("线程1看到的global_variable:", global_variable)
# 线程2,写数据
def write_data2():
global global_variable
for x in range(1000000):
global_variable += 1
print("线程2看到的global_variable:", global_variable)
# 主线程
def main():
t1 = Thread(target=write_data1)
t2 = Thread(target=write_data2)
t1.start()
t2.start()
t1.join()
t2.join()
print("主线程里看到的global_variable:", global_variable)
if __name__ == '__main__':
main()
代码区访问临界资源
from threading import Thread
# 定义两个全局变量
value1 = 0
value2 = 0
def writer_worker():
global value1
global value2
count = 0
while 1:
count += 1
# 使用两句代码改变那两个全局变量
value1 = count
value2 = count
def check_worker():
while 1:
# 时间片轮算法导致value2 != value1,可以使用互斥锁可解决这一问题
if value2 != value1:
print(f"value1:{value1} value2:{value2}")
if __name__ == '__main__':
t1 = Thread(target=writer_worker, name="写线程")
t2 = Thread(target=check_worker, name="读线程")
t1.start()
t2.start()
如何解决访问临界资源
- 使用python内置的原子结构设计临界资源。
- list、dict的操作方法
- queue.Queue的队列结构
- 使用Lock(锁)、Semaphore(信号量)。
- Lock对象(最常用的)
- Event对象(事件对象)
- Timer对象(定时器对象)
线程上的Lock对象
- 锁机制是操作系统底层提供的一种互斥访问的机制。
- 当一个任务获取到未锁定状态的锁时,任务继续运行;
- 当一个任务获取到以锁定状态的锁时,任务将被放入等待队列,直到这把锁被释放时被唤醒。
- threading提供了一个Lock类,来实现了这种锁机制。
Lock代码实例
import threading
value1 = 0
value2 = 0
def writer_worker():
global value1
global value2
count = 0
while True:
count += 1
# 上锁
# v_lock.acquire()
with v_lock:
value1 = count
value2 = count
# 解锁
# v_lock.release()
def check_worker():
while True:
# 上锁
v_lock.acquire()
if value2 != value1:
print(f"+++++v1:{value1}++v2:{value2}+++")
# 解锁
v_lock.release()
if __name__ == '__main__':
t1 = threading.Thread(target=writer_worker, name="写线程")
t2 = threading.Thread(target=check_worker, name="读线程")
# 买了一把锁
v_lock = threading.Lock()
t1.start()
t2.start()
线程共享数据(临界数据)安全问题解决
from threading import Thread, Lock
num = 0
# 定义两个函数
def add1():
global num
# 上锁 理论:锁住最小的单元
lock.acquire()
for i in range(1000000):
num += 1
# 第一步:num
# 第二步: temp = num+1
# 第三步:num = temp
# 解锁
lock.release()
print(f"add1:{num}")
def add2():
global num
# 上锁
lock.acquire()
for i in range(1000000):
num += 1
# 解锁
lock.release()
print(f"add2:{num}")
# 买一把锁
lock = Lock()
# 创建线程
t1 = Thread(target=add1)
t2 = Thread(target=add2)
# 启动线程
t1.start()
t2.start()
# 等等线程结束
t1.join()
t2.join()
print(f"任务结束:{num}")
类似于事务:要么都成功,要么都失败,就像同生共死那样,保证数据的完整性
死锁
-
产生原因:在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。
-
产生结果:尽管死锁很少发生,但一旦发生就会造成应用的停止响应。下面看一个死锁的例子
-
解决方法:设置超时时间
# coding=utf-8
import threading
import time
class MyThread1(threading.Thread):
def run(self):
if mutexA.acquire():
print(self.name + '----do1---up----')
time.sleep(1)
# 设置超时时间
if mutexB.acquire(blocking=True, timeout=2):
print(self.name + '----do1---down----')
mutexB.release()
mutexA.release()
class MyThread2(threading.Thread):
def run(self):
if mutexB.acquire():
print(self.name + '----do2---up----')
time.sleep(1)
if mutexA.acquire():
print(self.name + '----do2---down----')
mutexA.release()
mutexB.release()
mutexA = threading.Lock()
mutexB = threading.Lock()
if __name__ == '__main__':
t1 = MyThread1()
t2 = MyThread2()
t1.start()
t2.start()
线程同步
利用锁机制来解决线程同步,一旦同步,效率降低
import time
from threading import Thread, Lock
def f1():
while True:
if lock1.acquire():
print(1)
time.sleep(1)
# 解开2号锁
lock2.release()
def f2():
while True:
if lock2.acquire():
print(2)
time.sleep(1)
# 解开3号锁
lock3.release()
def f3():
while True:
if lock3.acquire():
print(3)
time.sleep(1)
# 解开1号
lock1.release()
# 锁
lock1 = Lock()
# 锁住2号
lock2 = Lock()
lock2.acquire()
# 锁住3号
lock3 = Lock()
lock3.acquire()
# 创建三个线程
t1 = Thread(target=f1)
t2 = Thread(target=f2)
t3 = Thread(target=f3)
# 启动线程
t1.start()
t2.start()
t3.start()
GIL
全局解释锁:为了保证线程的安全,在同一时刻,只有一个线程被CPU执行。
解决方案:调用C
分析什么时候使用多进程和多线程
多进程:CPU密集型,需要CPU参与大量运算
多线程:IO密集型 ,执行这个上任务的时候,CPU只需要很少,其它大部分时间都传输,文件传输
再加一条,如果你不知道你的代码到底算CPU密集型还是IO密集型,教你个方法:
multiprocessing这个module有一个dummy的sub module,它是基于multithread实现了multiprocessing的API。
假设你使用的是multiprocessing的Pool,是使用多进程实现了concurrency
from multiprocessing import Pool
如果把这个代码改成下面这样,就变成多线程实现concurrency
from multiprocessing.dummy import Pool
两种方式都跑一下,哪个速度快用哪个就行了。
网友评论