多任务:指的是一台电脑可以同时运行多个应用程序(一个应用程序可能有多个进程),是一种共享CPU的方法。
协同式多任务(cooperative multitasking):进程在执行过程中不受限制的占用cpu,不存在时间片概念,系统对cpu使用权的收回要靠进程主动上交。
抢先式多任务(preemptive multitasking):当一个新的进程开始时,在它的时间片(timeslice)之内,cpu的使用权是在这个进程手里的,当时间片结束时,系统要收回cpu的使用权做下一轮分配,即由系统进行cup使用权的分配与应用程序无关。
单核:CPU集成了一个运算核心,所有的任务都是由这个运算核心完成的,同一时间只能完成一个任务。
多核:CPU集成了多个运算核心,可以同一时间让多个运算核心同时工作完成多个任务。
并发:任务数多于CPU核心数,通过操作系统调度(Scheduling)方法,在很短时间内不断切换(时间片轮转,并有优先级算法等以任务的重要度优先考虑),实现在固定时间内多个任务共同执行。
并行:任务数小于CPU核心数,多个任务执行于不同的运算核心,真正的同时执行。
程序(program):是一种静态的实体,如封装的EXE文件,.py文件等。
进程(process):可以认为是运行的程序,是一个动态的实体,代表程序的执行过程,随着程序中指令的执行而不断的变化,在某个特定时刻的进程的内容被称为进程映像(process image),分为①正文段(text,被执行的机器指令,即代码)②用户数据段(user segment,存放进程在执行时直接操作的所有数据,包括进程使用的全部变量在内)③系统数据段(system segment,存放程序运行的环境,即进程的控制信息,是进程与程序的区别所在);进程有三种状态,就绪态、执行态、等待态(堵塞态),是多任务的一种实现方式。
线程(thread):线程(有时候称为轻量级进程)与进程类似,不过它们是在同一个进程下执行的,并共享相同的上下文,即将多任务的思想拓展到应用层面,将单个任务分解为小任务,再将这些任务分配给不同的CPU内核,提高进程速度;以python程序为例,其主代码顺序执行即主线程,在主线程过程中有一些函数、类等也需要新的线程去执行,称之为子线程。是多任务的一种轻量级的实现方式。
一、python中的多线程
python中的threading模块
python内置了threading模块(其前身为thread模块,但相对底层,在python3中更名为_thread),可用于多线程的执行,threading模块的类对象如下:
Thread 表示一个执行线程的对象
Lock 锁对象
RLock 可重入锁对象,使单一线程可以(再次)获得已持有的锁(递归锁)
Condition 条件变量对象,使得一个线程等待另外一个线程满足特定的条件,比如改变状态或者某个数据值
Event 条件变量的通用版本,任意数量的线程等待某个事件的发生,在该事件发生后所有的线程都将被激活
Semaphore 为线程间的有限资源提供一个计数器,如果没有可用资源时会被阻塞
BoundedSemaphore 与Semaphore相似,不过它不允许超过初始值
Timer 与Thread类似,不过它要在运行前等待一定时间
Barrier 创建一个障碍,必须达到指定数量的线程后才可以继续
threading.enumerate() 返回一个列表,列表中是当前的线程,其中主线程显示为MainThread
threading.current_thread() 返回当前线程,线程名在定义线程时指定,若不指定系统会默认写为Thread-1等,除了打印时用于显示没有别的用途
其中Thread类是线程的主要执行对象,其他类都属于同步机制。
# Thread类属性
name 线程名
ident 线程的标识符
daemon 布尔值,表示这个线程是否是守护线程
# Thread类的主要方法
__init__(group=None,target=None,name=None,args=(),kwargs={},verbose=None,daemon=None) 实例化一个线程对象,需要一个可调用的target对象,以及参数args或者kwargs。还可以传递name和group参数。daemon的值将会设定thread.daemon的属性
start() 开始执行该线程
run() 定义线程的方法(一般开发者在子类中重写此方法)
join(timeout=None) 直至启动的线程终止之前一直挂起;除非给出了timeout(单位秒),否则一直被阻塞
在python中使用多线程的常用方法有两种:
(1)创建Thread类实例,传递函数作为线程执行的内容。
def loop():
n = 0
while n < 5:
n = n + 1
print(threading.current_thread().name)
time.sleep(1)
t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()
print('end')
如上所示,直接生成Thread实例,调用start()方法即开始执行线程,join()方法意为等待子线程执行完毕后再执行主线程,否则'end'
会在loop函数调用前就打印出来。
(2)派生Thread的子类,并创建子类的实例。
class MyThread(threading.Thread):
def __init__(self,func,args,name=''):
threading.Thread.__init__(self)
self.func = func
self.name = name
self.args = args
def run(self):
print('开始执行',self.name,' 在:',ctime())
self.res = self.func(*self.args)
print(self.name,'结束于:',ctime())
def getResult(self):
return self.res
如上所示,自定义Thread的派生类,在其中定义run方法(一般在run方法中会直接调用start()/join()方法),其实只是上述方法的封装,但更适合面向对象的开发。
注:①主线程是可能在子线程之前结束的,但其并不会直接将子线程结束掉,join()只有在主线程需要等待子线程完成时候才是有用的(主线程与子线程是相互独立的,其都同属于一个进程,在默认情况下,进程会等待所有的子线程结束后再结束);
②只有调用实例的start方法时,才会创建并开始线程的执行;
③假如在一个函数中使用线程并使用变量传入参数,若子线程函数需要等待,而主线程很快的执行完毕,由于主线程执行完毕后与它关联的所有数据都会被回收,则子线程的参数就不存在了(正常情况下子线程会自动关闭,但特殊情况下其会报错)。
python多线程的同步机制
多线程与全局变量:在一个进程的多线程任务中,多个线程共享全局变量(创建Thread类实例时传入的变量就是全局变量,或处理磁盘上的文件等),即在一个线程中声明或更改了全局变量,按时间顺序另一个线程会获得更改后的全局变量。注意,在共享全局变量时,多个线程不断调用全局变量并且不断改变,可能会产生全局变量在不同进程中同时