一、介绍

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 使用

  1. import _thread,time
  2. class threadclass:
  3. ##创建线程
  4. def __init__(self):
  5. self._threadFunc = {}
  6. self._threadFunc['1'] = self.threadFunc1
  7. self._threadFunc['2'] = self.threadFunc2
  8. self._threadFunc['3'] = self.threadFunc3
  9. self._threadFunc['4'] = self.threadFunc4
  10. def threadFunc(self,selection,seconds):
  11. self._threadFunc[selection](seconds)
  12. def threadFunc1(self,seconds):
  13. _thread.start_new_thread(self.output,(seconds,1)) ##开始一个新的线程
  14. def threadFunc2(self,seconds):
  15. _thread.start_new_thread(self.output,(seconds,2))
  16. def threadFunc3(self,seconds):
  17. _thread.start_new_thread(self.output,(seconds,3))
  18. def threadFunc4(self,seconds):
  19. _thread.start_new_thread(self.output,(seconds,4))
  20. def output(self,seconds,number):
  21. for i in range(seconds):
  22. time.sleep(0.0001)
  23. print('----------',_thread.get_ident())
  24. print("进程 %d 已经运行"%number)
  25. mythread = threadclass()
  26. mythread.threadFunc('1',800)
  27. mythread.threadFunc('2',700)
  28. mythread.threadFunc('3',500)
  29. mythread.threadFunc('4',300)
  30. time.sleep(3.0)
  31. ##结果
  32. ---------- 14124
  33. 进程 4 已经运行
  34. ---------- 2164
  35. 进程 3 已经运行
  36. ---------- 10696
  37. 进程 2 已经运行
  38. ---------- 11292
  39. 进程 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 模块创建线程

  1. import threading,time
  2. def Process():
  3. for i in range(3):
  4. time.sleep(1)
  5. print('thread name is %s' % threading.current_thread().name)
  6. if __name__ == '__main__':
  7. print('-------------主线程开始-----------------')
  8. threads = [threading.Thread(target=Process) for i in range(4)] ##创建四个线程,存入列表
  9. for i in threads:
  10. i.start() ##开启线程
  11. for i in threads:
  12. i.join() ##等待子线程结束
  13. print('-------------主线程结束-----------------')
  14. ##结果
  15. -------------主线程开始-----------------
  16. thread name is Thread-2
  17. thread name is Thread-4
  18. thread name is Thread-3
  19. thread name is Thread-1
  20. thread name is Thread-2
  21. thread name is Thread-4
  22. thread name is Thread-1
  23. thread name is Thread-3
  24. thread name is Thread-2
  25. thread name is Thread-4
  26. thread name is Thread-1
  27. thread name is Thread-3
  28. -------------主线程结束-----------------

3.3.2 使用Thread子类创建线程

定义一个子类,使其继承Thread线程类来创建线程。

  1. #定义一个子类,使其继承Thread线程类来创建线程。
  2. import threading,time
  3. class SubThread(threading.Thread):
  4. def run(self):
  5. for i in range(3):
  6. time.sleep(1)
  7. msg = '子线程'+self.name + '执行,i ='+ str(i) ##name属性中保存的是当前线程的名字
  8. print(msg)
  9. if __name__ == '__main__':
  10. print('-------------主线程开始-----------------')
  11. t1 = SubThread() ##创建子线程t1
  12. t2 = SubThread() ##创建子线程t2
  13. t1.start() ##启动子线程t1
  14. t2.start() ##启动子线程t2
  15. t1.join() ##等待子线程t1
  16. t2.join() ##等待子线程t2
  17. print('-------------主线程结束-----------------')
  18. ##结果
  19. -------------主线程开始-----------------
  20. 子线程Thread-1执行,i =0
  21. 子线程Thread-2执行,i =0
  22. 子线程Thread-1执行,i =1
  23. 子线程Thread-2执行,i =1
  24. 子线程Thread-1执行,i =2
  25. 子线程Thread-2执行,i =2
  26. -------------主线程结束-----------------
  1. import threading,time,random
  2. class threadclass(threading.Thread):
  3. def run(self):
  4. x = 0
  5. y = random.randint(1,100)
  6. while x < y:
  7. x += 1
  8. time.sleep(0.1)
  9. print(self.name,y)
  10. t1 = time.time()
  11. for i in range(5):
  12. mythread = threadclass()
  13. mythread.start()
  14. mythread.join()
  15. t2 = time.time()
  16. print("进程运行结束",'用时:%d s'%(t2-t1))
  17. ##结果
  18. Thread-1 43
  19. Thread-2 1
  20. Thread-3 72
  21. Thread-4 11
  22. Thread-5 83
  23. 进程运行结束 用时:21 s

3.4 线程间通信

3.4.1 线程间数据共享

在一个进程内所有的线程共享数据。

  1. from threading import Thread
  2. import time
  3. def plus():
  4. print('-------子线程1开始---------')
  5. global g_num
  6. g_num += 50
  7. print('子线程1中g_num的值为:%d '%(g_num))
  8. print('-------子线程1结束---------')
  9. def mplus():
  10. print('-------子线程2开始---------')
  11. global g_num
  12. g_num -= 50
  13. print('子线程2中g_num的值为:%d '%(g_num))
  14. print('-------子线程2结束---------')
  15. g_num = 100 ##定义一个全局变量
  16. if __name__ == '__main__':
  17. print('-------主线程开始---------')
  18. print('主线程中g_num的值为:%d '%(g_num))
  19. t1 = Thread(target=plus) ##实例化线程t1
  20. t2 = Thread(target=mplus) ##实例化线程t2
  21. t1.start()
  22. t2.start()
  23. t1.join()
  24. t2.join()
  25. print('主线程中g_num的值为:%d '%(g_num))
  26. print('-------主线程结束---------')
  27. ##结果
  28. -------主线程开始---------
  29. 主线程中g_num的值为:100
  30. -------子线程1开始---------
  31. 子线程1g_num的值为:150
  32. -------子线程2开始---------
  33. -------子线程1结束---------
  34. 子线程2g_num的值为:100
  35. -------子线程2结束---------
  36. 主线程中g_num的值为:100
  37. -------主线程结束---------

3.4.2 互斥锁

线程可以对全局变量随意修改,可能导致多线程之间对全局变量的混乱,防止这种现象发生需要加互斥锁(Mutual exclusion,Mutex)。可以防止多个线程同时读写某一块内存区域。
互斥锁为资源引入一个状态:锁定和非锁定。
某个线程要更改共享数据的时候,先将其锁定,此时的资源状态为“锁定”,其他线程不能更改;
直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。
互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

在threading模块中使用Lock类处理锁定。一旦线程获取了锁,随后的尝试将其阻塞,直到释放为止,任何线程都可以释放它。
class threading.Lock

方法 说明
acquire(blocking=True, timeout=-1) 锁定,如果有必要,需要阻塞到锁释放为止
blocking如果为False,当无法获取锁定的时候将立即返回False,如果成功获取锁定将返回True
release() 释放锁,当锁定处于未锁定状态的时候,或者从与原本调用acquire()方法的不同线程调用此方法,将出现错误
locked() 如果获得了锁,则返回true
  1. ##互斥锁实现多人同时订购电影票的功能
  2. ##使用互斥锁实现多人同时订购电影票的功能
  3. from threading import Thread,Lock
  4. import time
  5. n = 100
  6. def task():
  7. global n
  8. mutex.acquire() ##上锁
  9. temp = n ##赋值给临时变量
  10. time.sleep(0.1)
  11. n = temp -1 ##数量-1
  12. print('购票成功,剩余%d张电影票'%n)
  13. mutex.release() ##释放锁
  14. if __name__ == "__main__":
  15. mutex = Lock() ##实例化Lock类
  16. t_l = []
  17. for i in range(10):
  18. t = Thread(target=task) ##实例化线程类
  19. t_l.append(t) ##将线程实例存入列表中
  20. t.start() ## 创建线程
  21. for t in t_l:
  22. t.join() ##等待子线程结束
  23. ##结果
  24. 购票成功,剩余99张电影票
  25. 购票成功,剩余98张电影票
  26. 购票成功,剩余97张电影票
  27. 购票成功,剩余96张电影票
  28. 购票成功,剩余95张电影票
  29. 购票成功,剩余94张电影票
  30. 购票成功,剩余93张电影票
  31. 购票成功,剩余92张电影票
  32. 购票成功,剩余91张电影票
  33. 购票成功,剩余90张电影票


ps:使用互斥锁的时候,要避免死锁
在多任务系统下,当一个或多个线程等待系统执行,而资源又被线程本身或其他线程占用的时候,就形成了死锁。

3.4.3 使用队列在线程间通信

https://docs.python.org/3.9/library/queue.html
使用queue模块的Queue队列实现线程间通信,通常应用于生产者和消费者模式。
产生数据的模块称为生产者,而处理数据的模块称为消费者,在生产者和消费者之间的缓冲区称为仓库,生产者负责往仓库运输商品,而消费者负责从仓库里取出商品。

  1. ##使用队列在线程间通信
  2. from queue import Queue
  3. import threading,time,random
  4. ##生产者类
  5. class Producer(threading.Thread):
  6. def __init__(self, name,queue):
  7. threading.Thread.__init__(self,name=name)
  8. self.data = queue
  9. def run(self):
  10. for i in range(5):
  11. print("生产者%s将产品%d加入队列!"%(self.getName(),i))
  12. self.data.put(i)
  13. time.sleep(random.random())
  14. print("生产者%s完成!!"%self.getName())
  15. ##消费者类
  16. class Consumer(threading.Thread):
  17. def __init__(self, name,queue):
  18. threading.Thread.__init__(self,name=name)
  19. self.data = queue
  20. def run(self):
  21. for i in range(5):
  22. val = self.data.get()
  23. print("消费者%s将产品%d从队列中取出!"%(self.getName(),val))
  24. time.sleep(random.random())
  25. print("消费者%s完成!!"%self.getName())
  26. if __name__ == "__main__":
  27. print("-------------主线程开始------------")
  28. queue = Queue() ##实例化队列
  29. producer = Producer('Producer',queue) ##实例化线程Producer,并传入队列作为参数
  30. consumer = Consumer('Consumer',queue) ##实例化线程consumer,并传入队列作为参数
  31. producer.start()
  32. consumer.start()
  33. producer.join()
  34. consumer.join()
  35. print("-------------主线程结束------------")
  36. ##结果
  37. -------------主线程开始------------
  38. 生产者Producer将产品0加入队列!
  39. 消费者Consumer将产品0从队列中取出!
  40. 生产者Producer将产品1加入队列!
  41. 消费者Consumer将产品1从队列中取出!
  42. 生产者Producer将产品2加入队列!
  43. 消费者Consumer将产品2从队列中取出!
  44. 生产者Producer将产品3加入队列!
  45. 消费者Consumer将产品3从队列中取出!
  46. 生产者Producer将产品4加入队列!
  47. 生产者Producer完成!!
  48. 消费者Consumer将产品4从队列中取出!
  49. 消费者Consumer完成!!
  50. -------------主线程结束------------