• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 众包
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
喵了个喵0507
博客园             管理     
【python】第一模块 步骤五 第二课、Python多线程

第二课、Python多线程

一、课程介绍

   1.1 课程概要

   章节概要

  • 进程、线程与并发
  • 对多核的利用
  • 实现一个线程
  • 线程之间的通信
  • 线程的调度和优化

   1.2 为什么要学习多线程

   (线程)使用场景

  • 快速高效的爬虫程序
  • 多用户同时访问的web服务
  • 电商秒杀、抢购活动
  • 物联网传感器监控服务器

   线程vs进程vs协程

  重要性

  • 跳槽、面试、决定薪资高度
  • 解决“效率”问题
  • Python的GIL导致的系列问题
  • 通常会混合使用(多进程+协程)

 

二、线程的实现及相关操作

   2.1 线程的介绍

  进程

  • 是一个执行中的程序
  • 每个进程都拥有自己的地址空间、内存、数据栈以及其他用于跟踪执行的辅助数据
  • 操作系统管理及上所有进程的执行,并为这些进程合理的分配时间
  • 进程也可通过派生(fork或spawn)新的进程来执行其他任务

  线程

  • 在同一个进程下执行,并共享相同的上下文
  • 一个进程中的各个线程与主线程共享同一片数据空间
  • 线程包括开始、执行顺序和结束三部分
  • 它可以被抢占(中断)和临时挂起(也成为睡眠)————让步
  • 一般是以并发方式执行

  并发

  • 是一种属性——程序、算法或问题的属性
  • 并行是指并发问题的可能方法之一
  • 如果两个事件互不影响,则两个事件是并发的

 

   2.2 对多核的利用及GIL概念

  对多核的利用

  • 单核CPU系统中,不存在真正的并发
  • GIL——全局解释器锁
  • GIL只是强制在任何时候只有一个线程可以执行Python代码
  • I/O密集型应用与CPU密集型应用

  GIL执行顺序

  1. 设置GIL
  2. 切换进一个线程去运行
  3. 执行下面操作之一:
    1. 指定数量的字节码指令
    2. 线程主动让出控制权(可以调用time.sleep(0)来完成)
  4. 把线程设置回睡眠状态(切换出线程)
  5. 解锁GIL
  6. 重复上述步骤

 

   2.3 线程的两种实现方式

  实现一个线程

  • 用threading模块代替thread模块
  • 用threading.Tread创建线程
  • start()启动线程
  • jion()挂起线程

  threading模块的对象

对象 描述
Thread 表示一个执行线程的对象
Lock 锁原语对象(和thread模块中的锁一样)
RLock 可重入锁对象,使单一线程可以(再次)获得已持有的锁(递归锁)
Condition 条件变量对象,使得一个线程等待另一个线程满足特定的“条件”,比如改变状态或某个数据值
Event 条件变量的通用版本,任意数量的线程等待某个事件的发生,在该事件发生后所有线程将被激活
Semaphore 为线程间共享的有限资源提供了一个“计数器”,如果没有可用资源时会被阻塞
BoundedSemap hore 与Semaphore相似,不过它不允许超过初始值
Timer 与Thread相似,不过它要再运行前等待一段时间
Barrier 创建一个”障碍“,必须达到指定数量的线程后才可以继续

 

 

 

 

 

 

 

  Thread对象数据属性

属性 描述
name 线程名
ident 线程的标识符
daemon 布尔标志,表示这个线程是否是守护线程

 

 

 

  Thread对象方法

属性 描述
__init__() 实例化一个线程对象,需要有一个可调用的target,以及其参数args或kwargs
start() 开始执行该线程
run() 定义线程功能的方法(通常在子类中被应用开发者重写)
jion(timeout=None) 直至启动的线程终止之前一直挂起;除非给出了timeout(秒),否则会一直阻塞
getName() 返回线程名
setName(name) 设定线程名
isAlivel/is_alive() 布尔标志,表示这个线程是否还存活
isDaemon() 如果是守护线程,则返回True;否则,返回False
setDaemon() 把线程的守护标志设定为布尔值daemonic(必须在线程start()之前调用)

 

 

 

 

 

 

 

  实现一个线程

 1 import threading
 2 import time
 3 
 4 
 5 def loop():
 6     """ 新的线程执行的代码 """
 7     n = 0
 8     while n < 5:
 9         print(n)
10         now_thread = threading.current_thread()
11         print('now thread name : {0}'.format(now_thread.name))
12         time.sleep(1)
13         n += 1
14 
15 
16 def use_thread():
17     """ 使用线程来实现 """
18     # 当前正在执行的线程名称
19     now_thread = threading.current_thread()
20     print('now thread name : {0}'.format(now_thread))
21     t = threading.Thread(target=loop, name='loop_thread')
22     # 启动线程
23     t.start()
24     # 挂起线程
25     t.join()
26 
27 
28 if __name__ =='__main__':
29     use_thread()
 1 import threading
 2 import time
 3 
 4 
 5 class LoopThread(threading.Thread):
 6     """ 自定义线程 """
 7 
 8     n = 0
 9 
10     def run(self):
11         while self.n < 5:
12             print(self.n)
13             now_thread = threading.current_thread()
14             print('now thread name : {0}'.format(now_thread.name))
15             time.sleep(1)
16             self.n += 1
17 
18 
19 if __name__ == '__main__':
20     # 当前正在执行的线程名称
21     now_thread = threading.current_thread()
22     print('now thread name : {0}'.format(now_thread.name))
23     t = LoopThread(name='loop_thread_oop')
24     t.start()
25     t.join()

 

  2.4 多线程并发问题

   实现多个线程

  • 最后为什么不是0?
 1 import threading
 2 import time
 3 
 4 
 5 
 6 # 我的银行账户
 7 balance = 0
 8 
 9 
10 def change_it(n):
11     """改变我的余额"""
12     global balance
13     balance = balance + n
14     time.sleep(2)
15     balance = balance - n
16     time.sleep(1)
17     print('--------->{0}'.format(n))
18 
19 
20 class ChangeBalanceThread(threading.Thread):
21     """
22     改变银行余额的线程
23     """
24 
25     def __init__(self, num, *args, **kwargs):
26         super().__init__(*args, **kwargs)
27         self.num = num
28 
29     def run(self):
30         for i in range(1000):
31             change_it(self.num)
32 
33 
34 if __name__ == '__main__':
35     t1 = ChangeBalanceThread(5)
36     t2 = ChangeBalanceThread(8)
37     t1.start()
38     t2.start()
39     t1.join()
40     t2.join()
41     print('the last: {0}'.format(balance))

 

  2.5 多线程中的锁

  多线程中的锁实现

  • Lock()
  • Rlock()
  • Condition()
 1 import threading
 2 import time
 3 
 4 
 5 # 获得一把锁
 6 my_lock = threading.Lock()
 7 your_lock = threading.RLock()
 8 
 9 # 我的银行账户
10 balance = 0
11 
12 
13 def change_it(n):
14     """ 改变我的余额 """
15     global balance
16 
17     # 方式一,使用with
18     with your_lock:
19         balance = balance + n
20         time.sleep(2)
21         balance = balance - n
22         time.sleep(1)
23         print('-N---> {0}; balance: {1}'.format(n, balance))
24 
25     # 方式二
26     # try:
27     #     print('start lock')
28     #     # 添加锁
29     #     your_lock.acquire()
30     #     print('locked one ')
31     #     # 资源已经被锁住了,不能重复锁定, 产生死锁
32     #     your_lock.acquire()
33     #     print('locked two')
34     #     balance = balance + n
35     #     time.sleep(2)
36     #     balance = balance - n
37     #     time.sleep(1)
38     #     print('-N---> {0}; balance: {1}'.format(n, balance))
39     # finally:
40     #     # 释放掉锁
41     #     your_lock.release()
42     #     your_lock.release()
43 
44 
45 class ChangeBalanceThread(threading.Thread):
46     """
47     改变银行余额的线程
48     """
49 
50     def __init__(self, num, *args, **kwargs):
51         super().__init__(*args, **kwargs)
52         self.num = num
53 
54     def run(self):
55         for i in range(100):
56             change_it(self.num)
57 
58 
59 if __name__ == '__main__':
60     t1 = ChangeBalanceThread(5)
61     t2 = ChangeBalanceThread(8)
62     t1.start()
63     t2.start()
64     t1.join()
65     t2.join()
66     print('the last: {0}'.format(balance))

 

  2.6 线程的调度和优化

  线程的调度和优化

 1 import time
 2 import threading
 3 from concurrent.futures import ThreadPoolExecutor
 4 from multiprocessing.dummy import Pool
 5 
 6 
 7 def run(n):
 8     """ 线程要做的事情 """
 9     time.sleep(2)
10     print(threading.current_thread().name, n)
11 
12 
13 def main():
14     """ 使用传统的方法来做任务 """
15     t1 = time.time()
16     for n in range(100):
17         run(n)
18     print(time.time() - t1)
19 
20 
21 def main_use_thread():
22     """ 使用线程优化任务 """
23     # 资源有限,最多只能跑10个线程
24     t1 = time.time()
25     ls = []
26     for count in range(10):
27         for i in range(10):
28             t = threading.Thread(target=run, args=(i,))
29             ls.append(t)
30             t.start()
31 
32         for l in ls:
33             l.join()
34     print(time.time() - t1)
35 
36 
37 def main_use_pool():
38     """ 使用线程池来优化 """
39     t1 = time.time()
40     n_list = range(100)
41     pool = Pool(10)
42     pool.map(run, n_list)
43     pool.close()
44     pool.join()
45     print(time.time() - t1)
46 
47 
48 def main_use_executor():
49     """ 使用 ThreadPoolExecutor 来优化"""
50     t1 = time.time()
51     n_list = range(100)
52     with ThreadPoolExecutor(max_workers=10) as executor:
53         executor.map(run, n_list)
54     print(time.time() - t1)
55 
56 
57 if __name__ == '__main__':
58     # main()
59     # main_use_thread()
60     # main_use_pool()
61     main_use_executor()

 

三、进程介绍

  3.1 进程介绍

  进程

  • 是一个执行中的程序
  • 每个进程都拥有自己的地址空间、内存、数据栈以及其他用于跟踪执行的辅助数据
  • 操作系统管理其上所有进程的执行,并为这些进程合理的分配时间
  • 进程也可通过派生(fork或spawn)新的进程来执行其他任务

  章节概要

  • 进程模块介绍
  • 进程的实现
  • 进程之间的通信
  • 使用进程池

  进程模块介绍

  • 我们使用multiprocessing实现多进程代码
  • 用multiprocessing.Process创建进程
  • start()启动进程
  • join()挂起进程
  • os.getpid()获得进程的ID

 

  3.2 进程的实现

  进程的实现

 1 import os
 2 import time
 3 from multiprocessing import Process
 4 
 5 
 6 def do_sth(name):
 7     """
 8     进程要做的事情
 9     :param name: str 进程的名称
10     """
11     print('进程的名称:{0}, pid: {1}'.format(name, os.getpid()))
12     time.sleep(150)
13     print('进程要做的事情')
14 
15 
16 class MyProcess(Process):
17 
18     def __init__(self, name, *args, **kwargs):
19         self.my_name = name
20         # print(self.name)
21         super().__init__(*args, **kwargs)
22         print(self.name)
23 
24     def run(self):
25         print('MyProcess进程的名称:{0}, pid: {1}'.format(
26             self.my_name, os.getpid()))
27         time.sleep(150)
28         print('MyProcess进程要做的事情')
29 
30 
31 if __name__ == '__main__':
32     # p = Process(target=do_sth, args=('my process', ))
33     p = MyProcess('my process class')
34     # 启动进程
35     p.start()
36     # 挂起进程
37     p.join()

 

  3.3 进程之间的通信

  进程之间的通信

  • 通过Queue、Pipes等实现进程之间的通信
 1 from multiprocessing import Process, Queue, current_process
 2 
 3 import random
 4 import time
 5 
 6 
 7 class WriteProcess(Process):
 8     """ 写的进程 """
 9     def __init__(self, q, *args, **kwargs):
10         self.q = q
11         super().__init__(*args, **kwargs)
12 
13     def run(self):
14         """ 实现进程的业务逻辑 """
15         # 要写的内容
16         ls = (
17             "第一行内容",
18             "第2行内容",
19             "第3行内容",
20             "第4行内容",
21         )
22         for line in ls:
23             print('写入内容: {0} - {1}'.format(line, current_process().name))
24             self.q.put(line)
25             # 每写入一次,休息1-5秒
26             time.sleep(random.randint(1, 5))
27 
28 
29 class ReadProcess(Process):
30     """ 读取内容进程 """
31     def __init__(self, q, *args, **kwargs):
32         self.q = q
33         super().__init__(*args, **kwargs)
34 
35     def run(self):
36         while True:
37             content = self.q.get()
38             print('读取到的内容:{0} - {1}'.format(content, self.name))
39 
40 
41 if __name__ == '__main__':
42     # 通过Queue共享数据
43     q = Queue()
44     # 写入内容的进程
45     t_write = WriteProcess(q)
46     t_write.start()
47     # 读取进程启动
48     t_read = ReadProcess(q)
49     t_read.start()
50 
51     t_write.join()
52     # t_read.join()
53 
54     # 因为读的进程是死循环,无法等待其结束,只能强制终止
55     t_read.terminate()

 

  3.4 多进程中的锁

   多进程中的锁

 

  • Lock()
  • Rlock()
  • Condition()
 1 import random
 2 from multiprocessing import Process, Lock, RLock
 3 
 4 import time
 5 
 6 
 7 class WriteProcess(Process):
 8     """ 写入文件 """
 9 
10     def __init__(self, file_name, num, lock, *args, **kwargs):
11         # 文件的名称
12         self.file_name = file_name
13         self.num = num
14         # 锁对象
15         self.lock = lock
16         super().__init__(*args, **kwargs)
17 
18     def run(self):
19         """ 写入文件的主要业务逻辑 """
20         with self.lock:
21         # try:
22         #     # 添加锁
23         #     self.lock.acquire()
24         #     print('locked')
25         #     self.lock.acquire()
26         #     print('relocked')
27             for i in range(5):
28                 content = '现在是: {0} : {1} - {2} \n'.format(
29                     self.name,
30                     self.pid,
31                     self.num
32                 )
33                 with open(self.file_name, 'a+', encoding='utf-8') as f:
34                     f.write(content)
35                     time.sleep(random.randint(1, 5))
36                     print(content)
37         # finally:
38         #     # 释放锁
39         #     self.lock.release()
40         #     self.lock.release()
41 
42 
43 if __name__ == '__main__':
44     file_name = 'test.txt'
45     # 锁的对象
46     lock = RLock()
47     for x in range(5):
48         p = WriteProcess(file_name, x, lock)
49         p.start()

 

  3.5 使用进程池

   使用进程池

 1 import random
 2 from multiprocessing import current_process, Pool
 3 
 4 import time
 5 
 6 
 7 def run(file_name, num):
 8     """
 9     进程执行的业务逻辑
10     往文件中写入数据
11     :param file_name: str 文件名称
12     :param num: int 写入的数字
13     :return: str 写入的结果
14     """
15     with open(file_name, 'a+', encoding='utf-8') as f:
16         # 当前的进程
17         now_process = current_process()
18         # 写入的内容
19         conent = '{0} - {1}- {2}'.format(
20             now_process.name,
21             now_process.pid,
22             num
23         )
24         f.write(conent)
25         f.write('\n')
26         # 写完之后随机休息1-5秒
27         time.sleep(random.randint(1, 5))
28         print(conent)
29     return 'ok'
30 
31 
32 if __name__ == '__main__':
33     file_name = 'test_pool.txt'
34     # 进程池
35     pool = Pool(2)
36     rest_list = []
37     for i in range(20):
38         # 同步添加任务
39         # rest = pool.apply(run, args=(file_name, i))
40         rest = pool.apply_async(run, args=(file_name, i))
41         rest_list.append(rest)
42         print('{0}--- {1}'.format(i, rest))
43     # 关闭池子
44     pool.close()
45     pool.join()
46     # 查看异步执行的结果
47     print(rest_list[0].get())

 

四、协程的实现及通信

  4.1 协程介绍

  章节概要

  • 协程介绍
  • 协程的实现
  • 协程之间的通信

  协程介绍

  • 协程就是协同多任务
  • 协程在一个进程或者是一个线程中执行
  • 不需要锁机制
  • 对多核CPU的利用——多进程+协程

 

  4.2 协程的实现

  协程的实现

  • python3.5以前
    • 使用生成器(yield)来实现
 1 def count_down(n):
 2     """ 倒计时效果 """
 3     while n > 0:
 4         yield n
 5         n -= 1
 6 
 7 
 8 def yield_test():
 9     """ 实现协程函数 """
10     while True:
11         n = (yield )
12         print(n)
13 
14 
15 if __name__ == '__main__':
16     # rest = count_down(5)
17     # print(next(rest))
18     # print(next(rest))
19     # print(next(rest))
20     # print(next(rest))
21     # print(next(rest))
22     rest = yield_test()
23     next(rest)
24     rest.send('6666')
25     rest.send('6666')
  • python3.5以后
    • 使用async和await关键字实现

  async关键字

  • 定义特殊函数

    async def async_f():

      pass

  • 当被调用时,不执行里面的代码,而是返回一个协程对象
  • 在事件循环中调度其执行前,协程对象不执行任何操作

  await关键字

  • 等待协程执行完成
  • 当遇到阻塞调用的函数的时候,使用await方法将协程的控制权让出,以便loop调用其他的协程

  asyncio模块

  • get_event_loop()获得事件循环队列
  • run_until_complete()注册任务到队列
  • 在事件循环中调度其执行前,协程对象不执行任何操作
  • asyncio模块用于事件循环
 1 import asyncio
 2 
 3 
 4 async def do_sth(x):
 5     """ 定义协程函数 """
 6     print('等待中: {0}'.format(x))
 7     await asyncio.sleep(x)
 8 
 9 # 判断是否为协程函数
10 print(asyncio.iscoroutinefunction(do_sth))
11 
12 coroutine = do_sth(5)
13 # 事件的循环队列
14 loop = asyncio.get_event_loop()
15 # 注册任务
16 task = loop.create_task(coroutine)
17 print(task)
18 # 等待协程任务执行结束
19 loop.run_until_complete(task)
20 print(task)

 

  4.3 协程通信之嵌套调用

  协程之间的数据通信

  • 嵌套调用
 1 import asyncio
 2 
 3 
 4 async def compute(x, y):
 5     print('计算x +y => {0}+{1}'.format(x, y))
 6     await asyncio.sleep(3)
 7     return x + y
 8 
 9 
10 async def get_sum(x, y):
11     rest = await compute(x, y)
12     print('{0} + {1} = {2}'.format(x, y, rest))
13 
14 # 拿到事件循环
15 loop = asyncio.get_event_loop()
16 loop.run_until_complete(get_sum(1, 2))
17 loop.close()

 

  4.4 协程通信之队列

  协程之间的数据通信

  • 队列
 1 # 1. 定义一个队列
 2 # 2. 让两个协程来进行通信
 3 # 3. 让其中一个协程往队列中写入数据
 4 # 4. 让另一个协程从队列中删除数据
 5 
 6 import asyncio
 7 import random
 8 
 9 
10 async def add(store, name):
11     """
12     写入数据到队列
13     :param store: 队列的对象
14     :return:
15     """
16     for i in range(5):
17         # 往队列中添加数字
18         num = '{0} - {1}'.format(name, i)
19         await asyncio.sleep(random.randint(1, 5))
20         await store.put(i)
21         print('{2} add one ... {0}, size: {1}'.format(
22             num, store.qsize(), name))
23 
24 
25 async def reduce(store):
26     """
27     从队列中删除数据
28     :param store:
29     :return:
30     """
31     for i in range(10):
32         rest = await store.get()
33         print(' reduce one.. {0}, size: {1}'.format(rest, store.qsize()))
34 
35 
36 if __name__ == '__main__':
37     # 准备一个队列
38     store = asyncio.Queue(maxsize=5)
39     a1 = add(store, 'a1')
40     a2 = add(store, 'a2')
41     r1 = reduce(store)
42 
43     # 添加到事件队列
44     loop = asyncio.get_event_loop()
45     loop.run_until_complete(asyncio.gather(a1, a2, r1))
46     loop.close()

 

posted on 2020-02-17 19:56  喵了个喵0507  阅读(227)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3