线程简介
- 线程是低于进程的一种处理任务单位,有时也被称为轻量进程,是程序执行流的最小单元,可以说:一个进程中可以创建多个线程来处理多个任务
- 一个线程的组成部分可分为线程ID、当前系统指令指针、寄存器集合、堆栈组合。
- 线程是进程中的一个实体,被系统独立调度和分派的基本单位,线程不拥有私有的系统资源,操作系统存在一个或多个进程,每个进程都拥有各自独占CPU资源,不同的进程之间无法进行资源共享,但如果现在需要实现CPU资源共享,就可以通过线程技术实现
- 线程是比进程更加轻量级控制单元,创建和销毁线程的代价更小,利用线程可以提高进程的处理性能
- 在单个程序中同时运行多个线程完成不同的工作,被称为多线程技术
现代处理器都是多核的多线程执行程序看起来是同时进行但是实际上则是CPU在多个线程之间快速切换执行
threading模块实现多线程
使用其中的Thread类
使用格式:
t=threading.Thread(target=None,name=None,args=())
参数 | 描述 |
---|---|
target | 线程启动时调用的函数或方法 |
name | 线程名称 |
args | 函数需要传入的参数(元组形式) |
Thread对象的主要方法
方法 | 简介 |
---|---|
run() | 用于表示线程活动的方法 |
start() | 启动线程 |
join() | 等待至线程终止 |
isAlive() | 判断线程是否活动 |
getName() | 返回线程名称 |
setName() | 设置线程名称 |
函数式创建线程
创建线程时,只需要传入一个执行函数与函数的参数即可,下面例子使用Thread类来产生两个子线程并等待其结束
import threading
import time,os,random,math
def printnum(num):
for i in range(num):
print(f'{
threading.current_thread().getName()},{
i}')
time.sleep(1)
if __name__=='__main__':
t1=threading.Thread(target=printnum,args=(2,),name='thread1')
t2=threading.Thread(target=printnum,args=(3,),name='thread2')
t1.start()
t2.start()
t1.join()
t2.join()
print(f'{
threading.current_thread().getName()}线程结束')
运行结果
创建线程类
直接创建一个Thread的子类来创建一个线程对象以实现多线程
import threading,time
class mythread(threading.Thread):
def __init__(self,name,num):
threading.Thread.__init__(self)
self.name=name
self.num=num
def run(self): # 线程启动后自动调用run()方法
for i in range(self.num):
print((f'{
threading.current_thread().getName()},{
i}'))
time.sleep(1)
if __name__=='__main__':
t1=mythread('thread1',3)
t2=mythread('thread2',2)
t1.start()
t2.start()
t1.join()
t2.join()
print((f'{
threading.current_thread().getName()}线程结束'))
运行结果
threading模块中两个函数获取活跃线程信息
函数 | 描述 |
---|---|
active_count() | 获取当前活跃线程个数 |
encumerate() | 获取活跃线程信息,返回一个列表序列 |
守护线程
主线程需要等待子线程执行完毕后才能继续执行,如果子线程不使用join()函数,主线程和子线程就是一起运行的,之间没有依赖关系
使用格式:线程对象.setDaemon(True)
多线程编程中,如果将子线程设定为主线程的守护线程,则会在等待主线程运行完毕后被销毁,此时,守护线程运行的前提时住线程必须存在
import threading
import time
def run(taskname):
print(f'任务-{
taskname}')
time.sleep(2)
print(f'任务-{
taskname}执行完毕')
if __name__=='__main__':
for i in range(3):
thread=threading.Thread(target=run,args=(f'{
i}',))
thread.setDaemon(True)
thread.start()
print(f'线程结束:{
threading.current_thread().getName()},当前线程数量为:{
threading.active_count()}')
运行结果
可以看到主线程执行完毕后,程序没有等待守护线程执行完毕就退出了
线程终止
threading模块不提供线程的终止方法,也不支持直接停止线程,通过Thread()创建的线程是相互独立的,如果在主线程中启动子线程,那么两者也是独立执行的线程
线程终止方法
如果想在终止主线程的同时强制终止子线程,最简单的方法是将子线程设置为守护线程,这是停止线程的一种方式,还有其他的停止子线程的方法
- 生成线程对象,将复杂的业务放在循环中,给线程对象设置一个停止的标志位,一旦标志位达到预定的值,就退出循环,这样就能停止线程了
import threading
import time
class testthread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self._running=True # 设置线程标志位
def terminate(self):
self._running=False
def run(self):
count=1
threadname=threading.current_thread().getName()
while self._running:
print(f'线程名称:{
threadname},次数:{
count}')
count+=1
time.sleep(1)
if __name__=='__main__':
t1=testthread()
t1.start()
time.sleep(3) # 等待三秒,期间次数加3
t1.terminate() # 修改标志位的值,停止子线程
print('主线程结束')
2. 通过ctypes模块调用,在子线程中报出异常,使子线程退出
多线程的锁机制
锁机制的解决的问题
多线程同时修改全局变量会出现数据安全问题,简单地说就是有可能出现多个线程先后修改数据的情况,造成所得到的数据是不一致的,也称“脏数据”
案例一:两个线程同时修改一个数据
import threading
num=10
def change_num(m,counter):
global num
for i in range(counter):
num+=m
num-=m
if num!=10: # 创建一个警示语句,如果该语句被执行则说明
print(f'num的值为:{
num}')
break
if __name__=='__main__':
t1=threading.Thread(target=change_num,args=(10,500000),name='线程1')
t2=threading.Thread(target=change_num,args=(10,500000),name='线程2')
t1.start()
t2.start()
t1.join()
t2.join()
print(f'线程结束:{
threading.current_thread().getName()}')
运行结果
通过运行结果我们可以发现,change_num()函数先加后减应该保证num的值一直为10,但结果并不是这样,实际上,线程的调度由系统决定同时启动两个线程交替执行,只要次数足够多,num的结果就不一定为10了
互斥锁简介
针对线程安全,需要使用到互斥锁,某线程要修改某数据资源时先将其锁定,此时该资源的状态为“锁定”,其它线程不能更改,直到该线程释放该资源,其它线程才能再次锁定该资源,
互斥锁意义
互斥锁保证了每次只有一个线程进行写入操作,从而保证了数据的正确性
锁机制的核心代码
# 创建一个锁对象
lock1=threading.Lock()
# 锁定
lock1.acquire()
# 释放
lock1.release()
案例二:两个线程修改同一数据(加锁)
import threading
num=10
lock=threading.Loak()
def change_num(m,counter):
global num
for i in range(counter):
lock.acquire() # 获取锁
num+=m
num-=m
lock.release() # 获得锁的线程用完后一定要释放锁,否则其它线程就会一直等待下去,从而成为死线程
if num!=10:
print(f'num的值为:{
num}')
break
if __name__=='__main__':
t1=threading.Thread(target=change_num,args=(10,500000),name='线程1')
t2=threading.Thread(target=change_num,args=(10,500000),name='线程2')
t1.start()
t2.start()
t1.join()
t2.join()
print(f'线程结束:{
threading.current_thread().getName()}')
运行结果
在上面的案例二中我们在change_num()函数中添加了锁机制,这样当某个进程执行change_num()函数时,就会获得锁,而其它线程将会等待直到获得该锁,这样两个线程修改全局变量num时就不会产生冲突,保证了数据的安全性