# 这是学习廖雪峰老师 python 教程的学习笔记
1, 概览
多任务可以由多进程完成, 也可以由一个进程内的多线程完成. 进程是由若干线程组成的, 一个进程至少有一个线程.
由于线程是操作系统直接支持的执行单元, 因此, 高级语言通常都内置多线程的支持, Python 也不例外, 并且, Python 的线程是真正的 Posix Thread, 而不是模拟出来的线程.
Python 的标准库提供了两个模块:_thread 和 threading._thread 是低级模块. threading 是高级模块, 对_thread 进行了封装. 绝大多数情况下, 我们只需要使用 threading 这个高级模块.
1.1, 创建一个线程
启动一个线程就是把一个函数传入并创建 Thread 实例, 然后调用 start() 开始执行:
- import time, threading
- # 新线程执行的代码:
- def loop():
- print('thread %s is running...' % threading.current_thread().name) #打印当前线程实例, 此函数体内, 都为 LoopThread
- n = 0
- while n <5:
- n = n + 1
- print('thread %s>>> %s' % (threading.current_thread().name, n))
- time.sleep(1)
- print('thread %s ended.' % threading.current_thread().name)
- print('thread %s is running...' % threading.current_thread().name) #打印当前线程实例, 此处为 MainThread
- t = threading.Thread(target=loop, name='LoopThread') # 线程执行 loop() 函数, 线程名为'LoopThread'
- t.start() #启动线程
- t.join() #等待线程运行完毕
- print('thread %s ended.' % threading.current_thread().name)
threading 模块的 current_thread() 函数, 它永远返回当前线程的实例. 主线程实例的名字叫 MainThread, 子线程的名字在创建时指定. 如果不指定 Python 就自动给线程命名为 Thread-1,Thread-2......
2,Lock
多线程和多进程最大的不同在于, 多进程中, 同一个变量, 各自有一份拷贝存在于每个进程中, 互不影响, 而多线程中, 所有变量都由所有线程共享, 所以, 任何一个变量都可以被任何一个线程修改, 因此, 线程之间共享数据最大的危险在于多个线程同时改一个变量, 把内容给改乱了.
2.1, 更改全局变量 balance
- import time, threading
- balance = 0
- lock = threading.Lock() # threading.Lock(), 创建一个锁
- def change_it(n):
- # 先加后减, 结果应该为 0:
- global balance
- balance = balance + n
- balance = balance - n
- def run_thread(n):
- for i in range(100000):
- # 先要获取锁:
- lock.acquire()
- try:
- # 进行修改:
- change_it(n)
- finally:
- # 改完了一定要释放锁:
- lock.release()
- t1 = threading.Thread(target=run_thread, args=(5,)) #创建线程, 让 t1 指向这个线程
- t2 = threading.Thread(target=run_thread, args=(8,)) #创建线程, 让 t2 指向这个线程
- t1.start()
- t2.start()
- t1.join()
- t2.join()
- print(balance)
当多个线程同时执行 lock.acquire() 时, 只有一个线程能成功地获取锁, 然后继续执行代码, 其他线程就继续等待直到获得锁为止
锁的好处:
是确保了某段关键代码只能由一个线程从头到尾完整地执行
锁的坏处:
阻止了多线程并发执行, 包含锁的某段代码实际上只能以单线程模式执行, 效率就大大地下降了.
由于可以存在多个锁, 不同的线程持有不同的锁, 并试图获取对方持有的锁时, 可能会造成死锁, 导致多个线程全部挂起, 既不能执行, 也无法结束, 只能靠操作系统强制终止.
3, 多核 CPU
3.1, 死循环测试 CPU 使用率
- import threading, multiprocessing
- def loop(): # 死循环
- x = 0
- while True:
- x = x ^ 1
- for i in range(multiprocessing.cpu_count()): # multiprocessing.cpu_count()=cpu 逻辑核数量
- t = threading.Thread(target=loop)
- t.start()
启动与 CPU 核心数量相同的 N 个线程, 在 4 核 CPU 上可以监控到 CPU 占用率仅有 102%, 也就是仅使用了一核.
用 C,C++ 或 Java 来改写相同的死循环, 直接可以把全部核心跑满. 但 Python 不行
因为 Python 的线程虽然是真正的线程, 但解释器执行代码时, 有一个 GIL 锁: Global Interpreter Lock, 任何 Python 线程执行前, 必须先获得 GIL 锁, 然后, 每执行 100 条字节码, 解释器就自动释放 GIL 锁, 让别的线程有机会执行. 这个 GIL 全局锁实际上把所有线程的执行代码都给上了锁, 所以, 多线程在 Python 中只能交替执行, 即使 100 个线程跑在 100 核 CPU 上, 也只能用到 1 个核
Python 虽然不能利用多线程实现多核任务, 但可以通过多进程实现多核任务. 多个 Python 进程有各自独立的 GIL 锁, 互不影响.
4, 小结
多线程编程, 模型复杂, 容易发生冲突, 必须用锁加以隔离, 同时, 又要小心死锁的发生.
Python 解释器由于设计时有 GIL 全局锁, 导致了多线程无法利用多核. 多线程的并发在 Python 中就是一个美丽的梦.
来源: http://www.bubuko.com/infodetail-2603644.html