多任务的概念
线程基础
单线程执行
多线程执行
主线程会等待所有子线程结束后才结束
查看线程数量
线程 - 注意点
线程执行代码的封装
线程的执行顺序
总结
多任务的概念
什么叫 "多任务" 呢? 简单地说, 就是操作系统可以同时运行多个任务. 打个比方, 你一边在用浏览器上网, 一边在听 MP3, 一边在用 Word 赶作业, 这就是多任务, 至少同时有 3 个任务正在运行. 还有很多任务悄悄地在后台同时运行着, 只是桌面上没有显示而已.
现在, 多核 CPU 已经非常普及了, 但是, 即使过去的单核 CPU, 也可以执行多任务. 由于 CPU 执行代码都是顺序执行的, 那么, 单核 CPU 是怎么执行多任务的呢?
答案就是操作系统轮流让各个任务交替执行, 任务 1 执行 0.01 秒, 切换到任务 2, 任务 2 执行 0.01 秒, 再切换到任务 3, 执行 0.01 秒...... 这样反复执行下去. 表面上看, 每个任务都是交替执行的, 但是, 由于 CPU 的执行速度实在是太快了, 我们感觉就像所有任务都在同时执行一样.
真正的并行执行多任务只能在多核 CPU 上实现, 但是, 由于任务数量远远多于 CPU 的核心数量, 所以, 操作系统也会自动把很多任务轮流调度到每个核心上执行.
注意:
并发: 指的是任务数多余 CPU 核数, 通过操作系统的各种任务调度算法, 实现用多个任务 "一起" 执行(实际上总有一些任务不在执行, 因为切换任务的速度相当快, 看上去一起执行而已)
并行: 指的是任务数小于等于 CPU 核数, 即任务真的是一起执行的
线程基础
python 的 thread 模块是比较底层的模块, python 的 threading 模块是对 thread 做了一些包装的, 可以更加方便的被使用
单线程执行
- import time
- def test():
- print("test...")
- time.sleep(1)
- if __name__ == '__main__':
- for i in range(5):
- test()
执行效果: 程序在控制台每隔一秒打印 test...
多线程执行
- import time
- import threading
- def test():
- print("test...")
- time.sleep(1)
- if __name__ == '__main__':
- for i in range(5):
- t = threading.Thread(target=test)
- t.start() # 启动线程
执行效果: 程序在控制台一下子输出五行 test..., 等待 1 秒左右结束
说明:
可以明显看出使用了多线程并发的操作, 花费时间要短很多
当调用 start()时, 才会真正的创建线程, 并且开始执行
主线程会等待所有子线程结束后才结束
- import time
- import threading
- def playPhone():
- print('玩手机...')
- time.sleep(1)
- def eat():
- print("吃东西...")
- time.sleep(1)
- if __name__ == '__main__':
- print("-- 开始 --")
- t1 = threading.Thread(target=playPhone)
- t1.start()
- t2 = threading.Thread(target=eat)
- t2.start()
- print('-- 执行结束')
执行效果: 主线程阻塞 1 秒左右后程序结束, 说明主线程在等待其他线程执行完毕.
查看线程数量
- print('玩手机...')
- time.sleep(1)
- def eat():
- for i in range(10):
- print("吃东西...")
- time.sleep(1)
- if __name__ == '__main__':
- print("-- 开始 --")
- t1 = threading.Thread(target=playPhone)
- t1.start()
- t2 = threading.Thread(target=eat)
- t2.start()
- # 查看正在执行的线程数量
- while True:
- length = len(threading.enumerate())
- print("当前运行的线程数量:%d" % length)
- print("这些线程是:%s" % str(threading.enumerate()))
- if length == 1:
- break
- time.sleep(0.5)
- print('-- 执行结束')
在 python 中, 调用 threading.enumerate()能获取当前正在运行的所有线程, 返回值是一个 list, 调用 length()函数并传入该 list 对象就获取到当前运行线程的数量.
线程 - 注意点
线程执行代码的封装
通过上一篇, 能够看出, 通过使用 threading 模块能完成多任务的程序开发, 为了让每个线程的封装性更完美, 所以使用 threading 模块时, 往往会定义一个新的子类 class, 只要继承 threading.Thread 就可以了, 然后重写 run 方法.
示例如下:
- import time
- import threading
- class MyThread(threading.Thread):
- def run(self):
- for i in range(5):
- time.sleep(1)
- print("我是 %s@%d" % (self.name, i))
- if __name__ == "__main__":
- mt = MyThread()
- mt.start()
运行结果如下:
我是 Thread-1@0
我是 Thread-1@1
我是 Thread-1@2
我是 Thread-1@3
我是 Thread-1@4
python 的 threading.Thread 类有一个 run 方法, 用于定义线程的功能函数, 可以在自己的线程类中覆盖该方法. 而创建自己的线程实例后, 通过 Thread 类的 start 方法, 可以启动该线程, 交给 python 虚拟机进行调度, 当该线程获得执行的机会时, 就会调用 run 方法执行线程.
线程的执行顺序
- mport time
- import threading
- class MyThread(threading.Thread):
- def run(self):
- for i in range(5):
- time.sleep(1)
- print("我是 %s@%d" % (self.name, i))
- if __name__ == "__main__":
- for i in range(5):
- mt = MyThread()
- mt.start()
执行结果(运行的结果可能不一样, 但是大体是一致的):
我是 Thread-5@0
我是 Thread-2@0
我是 Thread-3@0
我是 Thread-4@0
我是 Thread-1@0
我是 Thread-3@1
我是 Thread-4@1
我是 Thread-2@1
我是 Thread-5@1
我是 Thread-1@1
我是 Thread-3@2
我是 Thread-4@2
我是 Thread-2@2
我是 Thread-5@2
我是 Thread-1@2
我是 Thread-3@3
我是 Thread-4@3
我是 Thread-2@3
我是 Thread-5@3
我是 Thread-1@3
我是 Thread-3@4
我是 Thread-4@4
我是 Thread-5@4
我是 Thread-2@4
我是 Thread-1@4
从代码和执行结果我们可以看出, 多线程程序的执行顺序是不确定的. 当执行到 sleep 语句时, 线程将被阻塞 (Blocked), 到 sleep 结束后, 线程进入就绪(Runnable) 状态, 等待调度. 而线程调度将自行选择一个线程执行. 上面的代码中只能保证每个线程都运行完整个 run 函数, 但是线程的启动顺序, run 函数中每次循环的执行顺序都不能确定.
总结
每个线程默认有一个名字, 尽管上面的例子中没有指定线程对象的 name, 但是 python 会自动为线程指定一个名字.
当线程的 run()方法结束时该线程完成.
无法控制线程调度程序, 但可以通过别的方式来影响线程调度的方式.
来源: https://www.cnblogs.com/zhangfengxian/p/python-thread.html