Python进阶——线程的理解

一、线程的调度机制

1、图解线程调度机制

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  经过两个线程操作后,再查看值

2

  • 使用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、消费者与生产者模式的概念
  • 把一个需要进程通信的问题分开考虑:
    生产者:只需要往队列里面放置产品(不需要关心如何消费,也不用关心是谁消费)
    消费者:
    只需要从队列里面拿出产品(不需要关心如何生产,也不用关心是谁生产)

5
注意:很明显,此处的队列都是公共资源且双方都要操作,那么必须保证不会出现竞争问题。

2、线程安全队列操作
  • q = queue.Queue()
  1. 入队:put(item)
  2. 出队:get()
  3. 结束任务:task_done()
  4. 等待完成: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()

6

三、线程池

1、线程池的概念

主线程: 相当于生产者,只管向线程池提交任务,并不关心线程池如何执行任务
线程池: 相当于消费者,负责接收任务,并交由线程自行抢占执行
7

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()

8

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)

  • 因为线程池里的线程随机抢任务,所以运行结果会不一样
    9
    10
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值