共计 4991 个字符,预计需要花费 13 分钟才能阅读完成。
线程概述
多线程相似于同时执行多个不同程序,多线程运行有如下长处:
应用线程能够把占据长时间的程序中的工作放到后盾去解决。
用户界面能够更加吸引人,这样比方用户点击了一个按钮去触发某些事件的解决,能够弹出一个进度条来显示解决的进度
程序的运行速度可能放慢
在一些期待的工作实现上如用户输出、文件读写和网络收发数据等,线程就比拟有用了。在这种状况下咱们能够开释一些宝贵的资源如内存占用等等。
线程在执行过程中与过程还是有区别的。每个独立的过程有一个程序运行的入口、程序执行序列和程序的进口。然而线程不可能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行管制。
每个线程都有他本人的一组 CPU 寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的 CPU 寄存器的状态。
指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器,线程总是在过程失去上下文中运行的,这些地址都用于标记领有线程的过程地址空间中的内存。
线程能够被抢占(中断)。
在其余线程正在运行时,线程能够临时搁置(也称为睡眠)— 这就是线程的让步。
线程就是在程序运行过程中,执行程序代码的一个分支,每个运行的程序至多都有一个线程
创立多线程:thread
调用 thread 模块中的 start_new_thread() 函数来产生新线程
thread.start_new_thread (function, args[, kwargs] )
function - 线程函数。args - 传递给线程函数的参数, 他必须是个 tuple 类型。kwargs - 可选参数。python 多线程的开启
start()
import threading
import time
def task():
time.sleep(1)
print("以后线程:", threading.current_thread().name)
if__name__ =='__main__':
for_inrange(5):
sub_thread = threading.Thread(target=task)
sub_thread.start()
线程之间执行是无序的
主线程会期待所有的子线程完结后才完结
主线程会期待所有的子线程完结后才完结,如果须要能够设置守护主线程
多线程的特点(共享全局变量)
threading.currentThread(): 返回以后的线程变量。threading.enumerate(): 返回一个蕴含正在运行的线程的 list。正在运行指线程启动后、完结前,不包含启动前和终止后的线程。threading.activeCount(): 返回正在运行的线程数量,与 len(threading.enumerate()) 有雷同的后果。
除了应用办法外,线程模块同样提供了 Thread 类来解决线程,Thread 类提供了以下办法:
run(): 用以示意线程流动的办法。start(): 启动线程流动。join([time]): 期待至线程停止。这阻塞调用线程直至线程的 join() 办法被调用停止 - 失常退出或者抛出未解决的异样 - 或者是可选的超时产生。isAlive(): 返回线程是否流动的。getName(): 返回线程名。setName(): 设置线程名。
自定义线程
自定义线程不能指定 target,因为自定义线程外面的工作都对立在 run 办法外面执行
启动线程对立调用 start 办法,不要间接调用 run 办法, 因为这样不是应用子线程去执行工作
import threading
# 自定义线程类
classMyThread(threading.Thread):
# 通过构造方法取接管工作的参数
def__init__(self, info1, info2):
# 调用父类的构造方法
super(MyThread, self).__init__()
self.info1 = info1
self.info2 = info2
# 定义自定义线程相干的工作
def test1(self):
print(self.info1)
deftest2(self):
print(self.info2)
# 通过 run 办法执行相干工作
def run(self):
self.test1()
self.test2()
创立自定义线程
my_thread = MyThread("测试 1","测试 2")
启动
my_thread.start()
资源竞争问题
多线程能够共享全局变量
多线程共享全局变量,很不便在多个线程间共享数据
因为多线程同时对全局变量进行操作,很容易呈现资源竞争问题
import threading
# 定义全局变量
g_num =0
# 循环一次给全局变量加 1
defsum_num1():
for i in range(1000000):
globalg_num
g_num +=1
print("sum1:", g_num)
# 循环一次给全局变量加 1
defsum_num2():
for i in range(1000000):
global g_num
g_num +=1
print("sum2:", g_num)
if__name__ =='__main__':
# 创立两个线程
first_thread = threading.Thread(target=sum_num1)
second_thread = threading.Thread(target=sum_num2)
# 启动线程
first_thread.start()
# 启动线程
second_thread.start()
咱们能够看到多线程同时对全局变量操作数据产生了谬误
多线程同时操作全局变量导致数据可能呈现谬误的起因剖析
两个线程 first_thread 和 second_thread 都要对全局变量 g_num(默认是 0) 进行加 1 运算,然而因为是多线程同时操作,有可能呈现上面状况:
在 g_num= 0 时,first_thread 获得 g_num=0。此时零碎把 first_thread 调度为”sleeping”状态,把 second_thread 转换为”running”状态,t2 也取得 g_num=0
而后 second_thread 对失去的值进行加 1 并赋给 g_num,使得 g_num=1
而后零碎又把 second_thread 调度为”sleeping”,把 first_thread 转为”running”。线程 t1 又把它之前失去的 0 加 1 后赋值给 g_num。
这样导致尽管 first_thread 和 first_thread 都对 g_num 加 1,但后果依然是 g_num=1
多线程资源竞争解决办法
线程同步
线程同步: 保障同一时刻只能有一个线程去操作全局变量 同步: 就是协同步调,按预约的先后秩序进行运行。如: 你说完,我再说, 好比现实生活中的对讲机
多个线程同时对同一个全局变量进行操作,会有可能呈现资源竞争数据谬误的问题
线程同步形式能够解决资源竞争数据谬误问题,然而这样有多任务变成了单任务。
互斥锁
threading 模块中定义了 Lock 变量,这个变量实质上是一个函数,能够不便的解决锁定:
创立锁
mutex = threading.Lock()
锁定
mutex.acquire()
开释
mutex.release()
import threading
# 定义全局变量
g_num =0
# 创立全局互斥锁
lock = threading.Lock()
# 循环一次给全局变量加 1
def sum_num1():
# 上锁
lock.acquire()
for i in range(1000000):
global g_num
g_num +=1
print("sum1:", g_num)
# 开释锁
lock.release()
# 循环一次给全局变量加 1
def sum_num2():
# 上锁
lock.acquire()
for iin range(1000000):
global g_num
g_num +=1
print("sum2:", g_num)
# 开释锁
lock.release()
if __name__ =='__main__':
# 创立两个线程
first_thread = threading.Thread(target=sum_num1)
second_thread = threading.Thread(target=sum_num2)
# 启动线程
first_thread.start()
second_thread.start()
提醒:加上互斥锁,哪个线程抢到这个锁咱们决定不了,哪个线程抢到锁哪个线程先执行,没有抢到的线程须要期待
# 加上互斥锁多任务霎时变成单任务,性能会降落,也就是说同一时刻只能有一个线程去执行
当一个线程调用锁的 acquire() 办法取得锁时,锁就进入“locked”状态。
每次只有一个线程能够取得锁。如果此时另一个线程试图取得这个锁,该线程就会变为“blocked”状态,称为“阻塞”,直到领有锁的线程调用锁的 release() 办法开释锁之后,锁进入“unlocked”状态。
线程调度程序从处于同步阻塞状态的线程中抉择一个来取得锁,并使得该线程进入运行(running)状态。
确保了某段要害代码只能由一个线程从头到尾残缺地执行
锁的害处:
多线程执行变成了蕴含锁的某段代码实际上只能以单线程模式执行,效率就大大地降落了
锁应用不好就容易呈现死锁状况
死锁
import threading
import time
# 创立互斥锁
lock = threading.Lock()
# 依据下标去取值,保障同一时刻只能有一个线程去取值
def get_value(index):
# 上锁
lock.acquire()
print(threading.current_thread())
my_list = [3,6,8,1]
# 判断下标开释越界
if index >=len(my_list):
print("下标越界:", index)
return
value = my_list[index]
print(value)
time.sleep(0.2)
# 开释锁
lock.release()
if __name__ =='__main__':
# 模仿大量线程去执行取值操作
for i in range(30):
sub_thread = threading.Thread(target=get_value,args=(i,))
sub_thread.start()
应用互斥锁的时候须要留神死锁的问题,要在适合的中央留神开释锁
死锁一旦产生就会造成利用的进行响应
线程优先级队列(Queue)
Python 的 Queue 模块中提供了同步的、线程平安的队列类,包含 FIFO(先入先出 ) 队列 Queue,LIFO(后入先出)队列 LifoQueue,和优先级队列 PriorityQueue。这些队列都实现了锁原语,可能在多线程中间接应用。能够应用队列来实现线程间的同步。
Queue 模块中的罕用办法:
Queue.qsize() 返回队列的大小
Queue.empty() 如果队列为空,返回 True, 反之 False
Queue.full() 如果队列满了,返回 True, 反之 False
Queue.full 与 maxsize 大小对应
Queue.get([block[, timeout]]) 获取队列,timeout 等待时间
Queue.get_nowait() 相当 Queue.get(False)
Queue.put(item) 写入队列,timeout 等待时间
Queue.put_nowait(item) 相当 Queue.put(item, False)
Queue.task_done() 在实现一项工作之后,Queue.task_done() 函数向工作曾经实现的队列发送一个信号
Queue.join() 实际上意味着等到队列为空,再执行别的操作
线程,你学废了吗?