一、线程的调度机制
1、图解线程调度机制
注:1秒=1000毫秒,不同的线程不是同时运行的,只是因为其运行速度较快,让我们有一种它们在同时运行的错觉。
2、实例
- 因为线程是一个Python程序里面的子单元,它们是可以共享变量作用域的。
- 线程只是独立的执行单元,因此作用域是可以共享的
- 就算是100个线程,它也是跑在一个程序里
import threading
a = 1 # 主线程
def worker():
global a
a = 2 # 子线程里面的操作
th = threading.Thread(target=worker)
th.start()
th.join()
print(a)
3、变量共享的竞争
import threading
counter = 0
def increase(n):
#同一个程序的线程之间,变量可以互相访问
global counter
for i in range(n):
counter += 1
def decrease(n):
#同一个程序的线程之间,变量可以互相访问
global counter
for i in range(n):
counter -= 1
th1 = threading.Thread(target=increase, args=(100000,))
th2 = threading.Thread(target=decrease, args=(100000,))
th1.start()
th2.start()
th1.join()
th2.join()
print(counter)
注:每次输出的结果都有可能不一样
4、线程锁
import threading
counter = 0 # 主线程中定义的公共变量
lock = threading.Lock() #获得一把锁
def increase(n):
#同一个程序的线程之间,变量可以互相访问
global counter
for i in range(n):
#所有要作为“原子”的操作,都要放在锁中进行
lock.acquire()
counter += 1
lock.release()
def decrease(n):
#同一个程序的线程之间,变量可以互相访问
global counter
for i in range(n):
# 所有要作为“原子”的操作,都要放在锁中进行
lock.acquire()
counter -= 1
lock.release()
th1 = threading.Thread(target=increase, args=(100000,))
th2 = threading.Thread(target=decrease, args=(100000,))
th1.start()
th2.start()
th1.join()
th2.join()
print(counter) # 0 经过两个线程操作后,再查看值
- 使用with lock来改进以上代码:
import threading
counter = 0 # 主线程中定义的公共变量
lock = threading.Lock() #获得一把锁
def increase(n):
#同一个程序的线程之间,变量可以互相访问
global counter
for i in range(n):
#所有要作为“原子”的操作,都要放在锁中进行
with lock:
counter += 1
def decrease(n):
#同一个程序的线程之间,变量可以互相访问
global counter
for i in range(n):
# 所有要作为“原子”的操作,都要放在锁中进行
with lock:
counter -= 1
th1 = threading.Thread(target=increase, args=(100000,))
th2 = threading.Thread(target=decrease, args=(100000,))
th1.start()
th2.start()
th1.join()
th2.join()
print(counter) # 0 经过两个线程操作后,再查看值
二、线程通信
1、队列的基本概念
一个入口,一个出口,先进先出的模式(FIFO)
2、消费者与生产者模式的概念
- 把一个需要进程通信的问题分开考虑:
①生产者:只需要往队列里面放置产品(不需要关心如何消费,也不用关心是谁消费)
②消费者: 只需要从队列里面拿出产品(不需要关心如何生产,也不用关心是谁生产)
注意:很明显,此处的队列都是公共资源且双方都要操作,那么必须保证不会出现竞争问题。
2、线程安全队列操作
- q = queue.Queue()
- 入队:put(item)
- 出队:get()
- 结束任务:task_done()
- 等待完成:join()
3、多线程的消费者与生产者模式
from queue import Queue
from random import randint
import threading, time
from threading import current_thread
# 生产者:往这个队列里加东西
# 消费者:往这个队列里拿东西
queue = Queue() # 唯一的关联就是这个队列
def producer(): #生产者
while True:
time.sleep(1) #生产者每一秒生产一次
rint = randint(1, 99)
print(current_thread().name, rint)
queue.put(rint)
def consumer():
while True:
time.sleep(2) # 消费者每两秒消费一次
rint = queue.get()
print(current_thread().name, rint)
queue.task_done()
p = threading.Thread(target=producer, name='p1')
c1 = threading.Thread(target=consumer, name='c1')
c2 = threading.Thread(target=consumer, name='c2')
c1.start()
c2.start()
p.start()
三、线程池
1、线程池的概念
主线程: 相当于生产者
,只管向线程池提交任务,并不关心线程池如何执行任务
线程池: 相当于消费者
,负责接收任务,并交由线程自行抢占执行
2、线程池的简单实现
- 例如我们在做一个小说网站的爬虫时,如果不用多线程或者不用非阻塞套接字,必须要先爬完一本小说,才能爬第二个小说。
- 如果我们给每一个小说开一个线程,每个线程开起来就需要占用一定的内存,当多到一定程度的时候就会出现问题,例如:内存爆炸、切换浪费的时间太长等。
- 另一个解决问题的思路是:定好一定数量的线程,就用这些线程去爬取,用完一些就删掉一些线程,爬取新的小说再创建新的线程
- 但以上解决方案仍然存在一个问题,那就是:线程创建也是有开销的,所以不应该太多线程,也不应该频繁创建,于是我们想到了借助
可重复使用的线程
来解决这一问题。 - 基于“可复用线程”:
from queue import Queue
import threading
# 用一个队列,一方面不停的往队列里面丢任务,
# 另一方面不停的拿出任务来执行
queue = Queue()
def worker(): #消费者
while True: # 循环
task = queue.get() #拿函数
task()
def task1():
print('我是任务1哦!')
def task2():
print('我是任务2哦!')
def task3():
print('我是任务3哦!')
def task4():
print('我是任务4哦!')
# 主线程就当做生产者
th = threading.Thread(target=worker)
th.start()
queue.put(task1)
queue.put(task2)
queue.put(task3)
queue.put(task4)
queue.join()
3、线程池的实例
import threading
from queue import Queue
class ThreadPool:
def worker(self):
while True:
task = self.queue.get()
task()
self.queue.task_done()
def __init__(self, size):
self.queue = Queue()
for i in range(size):
th = threading.Thread(target=self.worker) # 每一个线程都是一个可复用的线程
th.start()
def apply_async(self, task):
self.queue.put(task)
def join(self):
self.queue.join()
pool = ThreadPool(4) #这个池子已经有了4个可以复用的线程
def task1():
print('我是任务1哦!')
def task2():
print('我是任务2哦!')
def task3():
print('我是任务3哦!')
def task4():
print('我是任务4哦!')
pool.apply_async(task1)
pool.apply_async(task2)
pool.apply_async(task3)
pool.apply_async(task4)
- 因为线程池里的线程随机抢任务,所以运行结果会不一样