线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。

基本使用

  • 导包
    • import threading
  • 实例化 ,传入函数名,参数。参数为元组形式传递
    • t = threading.Thread(target=func, args=(arg1, ))
  • 开启线程 ,不能使用run方法,否则就只是普通的调用函数
    • t.start()
  • 线程等待,等待子线程结束、主线程再继续执行
    • t.join()
  • 守护线,主线程不会等待子线程结束
    • t.setdaemon(True)
  • 查看线程数量
    • t.enumerate()


继承方式创建线程

  1. import threading
  2. class Producer(threading.Thread):
  3. def __init__(self, num):
  4. super().__init__()
  5. self.num = num
  6. def run(self):
  7. for i in range(self.num):
  8. print(f"producer {i}\n")
  9. class Consumer(threading.Thread):
  10. def __init__(self, num):
  11. super().__init__()
  12. self.num = num
  13. def run(self):
  14. for i in range(self.num):
  15. print(f"consumer {i}\n")
  16. if __name__ == '__main__':
  17. p = Producer(5)
  18. c = Consumer(3)
  19. p.start()
  20. c.start()
  21. p.join()
  22. c.join()

image.png

  1. - 参数传递需要初始化基类
  2. - 主线程不会等待子线程结束后再结束
  3. - 子线程在运行结束后会销毁
  4. - 不使用run方法开启线程,如果使用run方式只是普通的调用函数

线程间通信

  • 线程之间可以共享全局变量,如下代码,如果数据正确最终num应该为2000001,但显然结果并不是。
  1. import threading
  2. num = 1
  3. def func1():
  4. global num
  5. for i in range(1000000):
  6. num += 1
  7. def func2():
  8. global num
  9. for i in range(1000000):
  10. num += 1
  11. if __name__ == '__main__':
  12. t = threading.Thread(target=func1)
  13. t1 = threading.Thread(target=func2)
  14. t.start()
  15. t1.start()
  16. t.join()
  17. t1.join()
  18. print("num",num)
  1. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/1325594/1594280622245-b85d6367-0a27-4505-b96c-ef48113a8541.png#align=left&display=inline&height=68&margin=%5Bobject%20Object%5D&name=image.png&originHeight=144&originWidth=1207&size=42080&status=done&style=none&width=573)
  • 多线程对同一数据进行写操作时会造成紊乱,所以需要线程需要进行同步

线程锁

  • 某个线程要更改共享数据时,先将其锁定,此时资源的状态为”锁定”,其他线程不能改变,只到该线程释放资源,将资源的状态变成”非锁定”,其他的线程才能再次锁定该资源。
  • 互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
  • 方法
    • mutex = threading.Lock()
    • mutex.acquire()
    • mutex.release()
  1. import threading
  2. num = 1
  3. def func1(lock):
  4. global num
  5. for i in range(1000000):
  6. lock.acquire()
  7. num += 1
  8. lock.release()
  9. def func2(lock):
  10. global num
  11. for i in range(1000000):
  12. lock.acquire()
  13. num += 1
  14. lock.release()
  15. if __name__ == '__main__':
  16. # 创建线程锁
  17. lock = threading.Lock()
  18. t = threading.Thread(target=func1, args=(lock,))
  19. t1 = threading.Thread(target=func2, args=(lock,))
  20. t.start()
  21. t1.start()
  22. t.join()
  23. t1.join()
  24. print("num",num)

image.png

  • 可见,对数据相同的数据操作上锁之后,这次的结果就是正确的了,保证了数据的准确性

队列(FIFO)

  • from queue import Queue 导入
  • q = Queue() 实例化
  • 方法
    • q.qsize() 返回队列大小
    • q.full() 队列是否满了
    • q.empty() 队列是否为空
    • q.put(item, timeout=1) item加入队列。等待时间1s
    • item = q.get() 取出数据
    • Queue.put_nowait(item) 相当 Queue.put(item, False)

      线程和进程

      Python多任务-线程 - 图4

  1. 本质区别
    1. 线程是任务调度和执行的最小单位
    2. 进程是程序资源调度单位,包含线程
  2. 包含关系
    1. 线程属于进程的一部分
    2. 进程可以拥有多个线程
  3. 内存开销
    1. 复制一份代码、资源创建子进程。进程之间拥有相互独立的内存控件,切换开销较大
    2. 线程共享资源,并且使用的是进程里的资源,切换开销较小
  4. 适用条件
    1. 线程适用于IO密集型操作,如爬虫
    2. 进程适用于计算密集型操作,如数据建模