一、介绍
1.1 线程是什么
当执行任何应用程序的时候,CPU会为应用程序创建一个进程(process)。
进程组成元素:
1.给应用程序保留的内存控件
2.一个应用程序计数器
3.一个应用程序打开的文件列表
4.一个存储应用程序内变量的调用堆栈
如果一个应用程序只有一个调用栈及一个计数器,那么该应用程序为单线程的应用程序
线程(Thread)是操作系统能够进行运算调度的最小单位,它被包含在进程中,是进程的实际运作单位。
一个线程是指进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每个线程并行执行不同的任务。
1.2 多线程
多线程用于同时执行多个不同的程序或任务,可以做到并行处理和提高程序执行能力
多线程的应用程序会创建一个函数,来执行需要重复执行多次的程序代码。然后创建一个线程执行该函数。一个线程(thread)是一个应用程序,用于在后台并执行多个耗时的动作。
在多线程的应用程序中,每一个线程的执行时间等于应用程序所花的CPU时间除以线程的数目。
线程彼此之间会共享数据,在更新数据之前,必须先将程序代码锁定,来实现线程的同步。
1.3 python中的线程模块
Python有两个线程接口:_thread模块与threading模块。
_thread模块提供低级的接口,用于支持小型的进程线程
threading模块是以threadd模块为基础,提供高级的接口
queue模块内的queue类,可以在多个线程中安全地移动Python对象
二、_thread 模块
2.1 介绍
https://docs.python.org/3.9/library/_thread.html
用于处理多个线程(也称为轻量级进程或任务),多个控件线程共享其全局数据空间,提供了简单的锁(也称为互斥体或二进制信号量)进行数据同步。
2.2 函数和常量
| 函数和常量 | 说明 |
|---|---|
| _thread.allocate_lock() | 创建并返回一个lckobj对象 lckobj对象有下面的三个方法: lckobj.acquire([flags]): 用来捕获一个lock lckobj.release(): 释放lock lckobj.locked(): 若对象成功锁定,则返回True,否则返回True |
| _thread.exit() | 抛出一个SystemExit,以终止线程的执行 它与sys.exit() 函数相同 |
| _thread.get_ident() | 读取目前线程的识别码,一个非零整数,没有直接的意义,当一个线程退出并创建另一个线程时,线程标识符可以被回收 |
| _thread.get_native_id() | 返回内核分配的当前线程的本机整数Thread ID,一个非负整数,用于在系统范围内唯一标识此特定线程(直到线程终止,然后OS可以回收该值 |
| _thread.start_new_thread(func,args [, kwargs] | 开始一个新的线程 线程使用参数列表args(必须为元组)执行function函数,选的kwargs参数指定关键字参数的字典 |
| _thread.interrupt_main() | 模拟signal.SIGINT信号到达主线程的效果,线程可以使用此功能来中断主线程 |
| _thread.stack_size([size] ) | 返回创建新线程时使用的线程堆栈大小 |
| _thread.LockType | 锁定对象的类型 |
2.3 使用
import _thread,timeclass threadclass:##创建线程def __init__(self):self._threadFunc = {}self._threadFunc['1'] = self.threadFunc1self._threadFunc['2'] = self.threadFunc2self._threadFunc['3'] = self.threadFunc3self._threadFunc['4'] = self.threadFunc4def threadFunc(self,selection,seconds):self._threadFunc[selection](seconds)def threadFunc1(self,seconds):_thread.start_new_thread(self.output,(seconds,1)) ##开始一个新的线程def threadFunc2(self,seconds):_thread.start_new_thread(self.output,(seconds,2))def threadFunc3(self,seconds):_thread.start_new_thread(self.output,(seconds,3))def threadFunc4(self,seconds):_thread.start_new_thread(self.output,(seconds,4))def output(self,seconds,number):for i in range(seconds):time.sleep(0.0001)print('----------',_thread.get_ident())print("进程 %d 已经运行"%number)mythread = threadclass()mythread.threadFunc('1',800)mythread.threadFunc('2',700)mythread.threadFunc('3',500)mythread.threadFunc('4',300)time.sleep(3.0)##结果---------- 14124进程 4 已经运行---------- 2164进程 3 已经运行---------- 10696进程 2 已经运行---------- 11292进程 1 已经运行
三、threading模块
3.1 介绍
https://docs.python.org/3.9/library/threading.html
该模块在较低级别的thread模块之上构造较高级别的线程接口。
Thread类表示在单独的控制线程中运行的活动,仅 重写此类的 _init()和run()方法。
创建线程对象后,必须通过调用线程的start()方法来开始其活动,一旦线程的活动开始,该线程即被视为“活动”。当其run()方法终止时,无论是正常还是通过引发未处理的异常,它都不再处于活动状态,is_alive() 方法测试线程是否处于活动状态。
其他线程可以调用线程的join()方法。这将阻塞调用线程,直到其join()方法被调用的线程终止。
线程可以标记为“守护程序线程”,该标志的重要性在于,仅保留守护程序线程时,整个Python程序都会退出。初始值是从创建线程继承的,可以通过daemon属性或守护程序构造函数参数设置该标志。
语法:class threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
group:值为None,为以后版本而保留
target:表示一个可调用对象,线程启动的时候,run() 方法将调用次对象,默认值为None,表示不会调用任何内容
name:当前线程的名称,默认创建一个Thread-N 的唯一名称
args:传递给target函数的参数元组
kwargs:传递给target函数的参数字典
3.2 函数
threading模块的函数如下
| 函数 | 说明 |
|---|---|
| threading.activeCount() | 返回活动中的线程对象数目 |
| threading.currentThread() | 返回目前控制的线程对象 |
| threading.enumerate() | 返回活动中的线程对象 |
| threading.main_thread() | 返回主线程对象,主线程是启动Python解释器的线程 |
每一个threading.Thread()类对象都有下面的方法
| 方法 | 说明 |
|---|---|
| threadobj.start() | 执行run方法,启动线程 |
| threadobj.run() | 此方法被start()方法调用,表示线程的活动 |
| threadobj.join([timeout]) | 此方法等待线程结束,将阻塞调用线程,直到调用其join() 方法的线程终止。 timeout的单位是秒 |
| threadobj.isActive() | 如果线程对象的run()方法已经执行,就返回1,否则就返回 |
3.3 使用
3.3.1 使用threading 模块创建线程
import threading,timedef Process():for i in range(3):time.sleep(1)print('thread name is %s' % threading.current_thread().name)if __name__ == '__main__':print('-------------主线程开始-----------------')threads = [threading.Thread(target=Process) for i in range(4)] ##创建四个线程,存入列表for i in threads:i.start() ##开启线程for i in threads:i.join() ##等待子线程结束print('-------------主线程结束-----------------')##结果-------------主线程开始-----------------thread name is Thread-2thread name is Thread-4thread name is Thread-3thread name is Thread-1thread name is Thread-2thread name is Thread-4thread name is Thread-1thread name is Thread-3thread name is Thread-2thread name is Thread-4thread name is Thread-1thread name is Thread-3-------------主线程结束-----------------
3.3.2 使用Thread子类创建线程
定义一个子类,使其继承Thread线程类来创建线程。
#定义一个子类,使其继承Thread线程类来创建线程。import threading,timeclass SubThread(threading.Thread):def run(self):for i in range(3):time.sleep(1)msg = '子线程'+self.name + '执行,i ='+ str(i) ##name属性中保存的是当前线程的名字print(msg)if __name__ == '__main__':print('-------------主线程开始-----------------')t1 = SubThread() ##创建子线程t1t2 = SubThread() ##创建子线程t2t1.start() ##启动子线程t1t2.start() ##启动子线程t2t1.join() ##等待子线程t1t2.join() ##等待子线程t2print('-------------主线程结束-----------------')##结果-------------主线程开始-----------------子线程Thread-1执行,i =0子线程Thread-2执行,i =0子线程Thread-1执行,i =1子线程Thread-2执行,i =1子线程Thread-1执行,i =2子线程Thread-2执行,i =2-------------主线程结束-----------------
import threading,time,randomclass threadclass(threading.Thread):def run(self):x = 0y = random.randint(1,100)while x < y:x += 1time.sleep(0.1)print(self.name,y)t1 = time.time()for i in range(5):mythread = threadclass()mythread.start()mythread.join()t2 = time.time()print("进程运行结束",'用时:%d s'%(t2-t1))##结果Thread-1 43Thread-2 1Thread-3 72Thread-4 11Thread-5 83进程运行结束 用时:21 s
3.4 线程间通信
3.4.1 线程间数据共享
在一个进程内所有的线程共享数据。
from threading import Threadimport timedef plus():print('-------子线程1开始---------')global g_numg_num += 50print('子线程1中g_num的值为:%d '%(g_num))print('-------子线程1结束---------')def mplus():print('-------子线程2开始---------')global g_numg_num -= 50print('子线程2中g_num的值为:%d '%(g_num))print('-------子线程2结束---------')g_num = 100 ##定义一个全局变量if __name__ == '__main__':print('-------主线程开始---------')print('主线程中g_num的值为:%d '%(g_num))t1 = Thread(target=plus) ##实例化线程t1t2 = Thread(target=mplus) ##实例化线程t2t1.start()t2.start()t1.join()t2.join()print('主线程中g_num的值为:%d '%(g_num))print('-------主线程结束---------')##结果-------主线程开始---------主线程中g_num的值为:100-------子线程1开始---------子线程1中g_num的值为:150-------子线程2开始----------------子线程1结束---------子线程2中g_num的值为:100-------子线程2结束---------主线程中g_num的值为:100-------主线程结束---------
3.4.2 互斥锁
线程可以对全局变量随意修改,可能导致多线程之间对全局变量的混乱,防止这种现象发生需要加互斥锁(Mutual exclusion,Mutex)。可以防止多个线程同时读写某一块内存区域。
互斥锁为资源引入一个状态:锁定和非锁定。
某个线程要更改共享数据的时候,先将其锁定,此时的资源状态为“锁定”,其他线程不能更改;
直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。
互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
在threading模块中使用Lock类处理锁定。一旦线程获取了锁,随后的尝试将其阻塞,直到释放为止,任何线程都可以释放它。class threading.Lock
| 方法 | 说明 |
|---|---|
| acquire(blocking=True, timeout=-1) | 锁定,如果有必要,需要阻塞到锁释放为止 blocking如果为False,当无法获取锁定的时候将立即返回False,如果成功获取锁定将返回True |
| release() | 释放锁,当锁定处于未锁定状态的时候,或者从与原本调用acquire()方法的不同线程调用此方法,将出现错误 |
| locked() | 如果获得了锁,则返回true |
##互斥锁实现多人同时订购电影票的功能##使用互斥锁实现多人同时订购电影票的功能from threading import Thread,Lockimport timen = 100def task():global nmutex.acquire() ##上锁temp = n ##赋值给临时变量time.sleep(0.1)n = temp -1 ##数量-1print('购票成功,剩余%d张电影票'%n)mutex.release() ##释放锁if __name__ == "__main__":mutex = Lock() ##实例化Lock类t_l = []for i in range(10):t = Thread(target=task) ##实例化线程类t_l.append(t) ##将线程实例存入列表中t.start() ## 创建线程for t in t_l:t.join() ##等待子线程结束##结果购票成功,剩余99张电影票购票成功,剩余98张电影票购票成功,剩余97张电影票购票成功,剩余96张电影票购票成功,剩余95张电影票购票成功,剩余94张电影票购票成功,剩余93张电影票购票成功,剩余92张电影票购票成功,剩余91张电影票购票成功,剩余90张电影票
ps:使用互斥锁的时候,要避免死锁。
在多任务系统下,当一个或多个线程等待系统执行,而资源又被线程本身或其他线程占用的时候,就形成了死锁。
3.4.3 使用队列在线程间通信
https://docs.python.org/3.9/library/queue.html
使用queue模块的Queue队列实现线程间通信,通常应用于生产者和消费者模式。
产生数据的模块称为生产者,而处理数据的模块称为消费者,在生产者和消费者之间的缓冲区称为仓库,生产者负责往仓库运输商品,而消费者负责从仓库里取出商品。
##使用队列在线程间通信from queue import Queueimport threading,time,random##生产者类class Producer(threading.Thread):def __init__(self, name,queue):threading.Thread.__init__(self,name=name)self.data = queuedef run(self):for i in range(5):print("生产者%s将产品%d加入队列!"%(self.getName(),i))self.data.put(i)time.sleep(random.random())print("生产者%s完成!!"%self.getName())##消费者类class Consumer(threading.Thread):def __init__(self, name,queue):threading.Thread.__init__(self,name=name)self.data = queuedef run(self):for i in range(5):val = self.data.get()print("消费者%s将产品%d从队列中取出!"%(self.getName(),val))time.sleep(random.random())print("消费者%s完成!!"%self.getName())if __name__ == "__main__":print("-------------主线程开始------------")queue = Queue() ##实例化队列producer = Producer('Producer',queue) ##实例化线程Producer,并传入队列作为参数consumer = Consumer('Consumer',queue) ##实例化线程consumer,并传入队列作为参数producer.start()consumer.start()producer.join()consumer.join()print("-------------主线程结束------------")##结果-------------主线程开始------------生产者Producer将产品0加入队列!消费者Consumer将产品0从队列中取出!生产者Producer将产品1加入队列!消费者Consumer将产品1从队列中取出!生产者Producer将产品2加入队列!消费者Consumer将产品2从队列中取出!生产者Producer将产品3加入队列!消费者Consumer将产品3从队列中取出!生产者Producer将产品4加入队列!生产者Producer完成!!消费者Consumer将产品4从队列中取出!消费者Consumer完成!!-------------主线程结束------------
