多任务实现方法:
- 多进程
- 多线程
- 一个进程内创建多个线程
线程是操作系统直接支持的执行单元,因此,高级语言中大多内置了多线程的支持,Python的多线程是真正的Posix Thread,而不是模拟出来的多线程
Python多线程实现
Python提供两个高级库:_thread和thread,_thread是低级模块,thread是高级模块,对_thread进行了封装, 大多数情况下使用thread
两种方法使用线程:
- 传入一个函数
- 继承自threading.Thread的类
方法一: 启动一个线程的方法:把一个函数传入并创建Thread实例,然后调用start()函数开始执行:
import time, threading
# 新线程执行的代码:
def loop():
print('thread %s is running...' % threading.current_thread().name)
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)
t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()
print('thread %s ended.' % threading.current_thread().name)
执行结果如下:
thread MainThread is running...
thread LoopThread is running...
thread LoopThread >>> 1
thread LoopThread >>> 2
thread LoopThread >>> 3
thread LoopThread >>> 4
thread LoopThread >>> 5
thread LoopThread ended.
thread MainThread ended.
由于任何进程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程,Python的threading模块有个current_thread()函数,它永远返回当前线程的实例。主线程实例的名字叫MainThread,子线程的名字在创建时指定,我们用LoopThread命名子线程。名字仅仅在打印时用来显示,完全没有其他意义,如果不起名字Python就自动给线程命名为Thread-1,Thread-2……
方法二: 使用继承自threading.Thread的类
import threading
import time
exitFlag = 0
class myThread (threading.Thread):
def __init__(self, threadID, name, counter):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self):
print "Starting " + self.name
# 获得锁,成功获得锁定后返回True
# 可选的timeout参数不填时,将一直阻塞知道获得锁定
# 否则超时后将返回False
threadLock.acquire()
print_time(self.name, self.counter, 5)
# 释放锁
threadLock.release()
print "Exiting " + self.name
def print_time(threadName, delay, counter):
while counter:
if exitFlag:
thread.exit()
time.sleep(delay)
print "%s: %s" % (threadName, time.ctime(time.time()))
counter -= 1
threadLock = threading.Lock()
threads = []
# 创建新线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
# 开启线程
thread1.start()
thread2.start()
# 添加线程到线程列表
threads.append(thread1)
threads.append(thread2)
# 等待所有线程完成
for t in threads:
t.join()
print "Exiting Main Thread"
执行结果如下:
几点注意的地方:
- 上面的程序我们使用了Lock(线程锁),对资源进行独占,锁的使用方法如下:
1. 创建一个锁实例: **threadLock = threading.Lock()
2. 给需要独占的资源上锁:
threadingLock.acquire() # 获得锁
此处添加需要独占的资源
threadingLock.release() # 资源用完后,需要释放锁 - 使用了join()函数,join()函数的作用总结如下:
1. 阻塞主进程无法执行join以后的语句,专注执行多线程,必须等待多线程执行完毕之后才能执行主线程的语句。
2. 多线程多join的情况下,依次执行各线程的join方法,前一个结束之后,才能执行后一个。(在上面的程序中,使用join()的这一条功能)
3. 无参数,则等待到该线程结束,才开始执行下一个线程的join。
4. 设置参数后,则等待该线程N秒之后不管该线程是否结束,就开始执行后面的主进程。 - run()函数:在创建线程后会自动执行run()函数
Lock锁
多进程与多线程最大的区别在于:多进程中,同一个变量,各自有一份拷贝存在于进程中,互不影响(可以理解为:多进程中,每个进程都有一个堆栈空间存放各自的运行环境);而在多线程中,所有变量均有所有线程共享,因此,多线程最大的危险在于多个线程更改同一个变量,导致内容变乱;
为了解决这个问题,python中有一个lock,可以让线程对某一个资源独享,通过
lock = threading.Lock()实现
balance = 0
lock = threading.Lock()
def run_thread(n):
for i in range(100000):
# 先要获取锁:
lock.acquire()
try:
# 放心地改吧:
change_it(n)
finally:
# 改完了一定要释放锁:
lock.release()
多个线程同时执行lock.acquire(),只有一个线程能够获得锁
获得锁的线程,用完了资源后,要及时释放锁,不然会导致其他线程一直被挂起,
死锁: 锁的机制可以确保资源的独占,但是也会使得某段代码只能以单线程的形式执行,不能并发,同时,不同线程可以有不同的锁,为了获得对方的锁,可能会导致死锁的情况,如:线程A锁住了记录1并等待记录2,而线程B锁住了记录2并等待记录1,这样两个线程就发生了死锁现象。
三个示例:
1. 未使用锁的示例
# 未使用锁的情况
class myThread (threading.Thread):
def __init__(self, threadID, name, counter):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self):
print "Starting " + self.name
print_time(self.name, self.counter, 5)
print "Exiting " + self.name
def print_time(threadName, delay, counter):
while counter:
if exitFlag:
thread.exit()
time.sleep(delay)
print "%s: %s" % (threadName, time.ctime(time.time()))
counter -= 1
# 创建新线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
# 开启线程
thread1.start()
thread2.start()
print "Exiting Main Thread"
执行结果:
几点注意:
在上面的程序中,我们未使用锁,也没有使用join()函数
- 从打印结果中可以看到,在最前面的三条打印中,显示的是:Starting Thread-1、Starting Thread-2和Exiting Main Thread。说明启动thread1和thread2之后,它们均进入到了sleep状态,这时继续往下执行了主线程,
2.从打印中可以看到Thread-1和Thread-2是交替执行的,并不是执行完Thread-1的全部代码后再执行Thread-2
2. 使用锁,但不使用join()的示例
class myThread (threading.Thread):
def __init__(self, threadID, name, counter):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self):
print "Starting " + self.name
# 获得锁,成功获得锁定后返回True
# 可选的timeout参数不填时,将一直阻塞知道获得锁定
# 否则超时后将返回False
threadLock.acquire()
print_time(self.name, self.counter, 5)
# 释放锁
threadLock.release()
print "Exiting " + self.name
def print_time(threadName, delay, counter):
while counter:
if exitFlag:
thread.exit()
time.sleep(delay)
print "%s: %s" % (threadName, time.ctime(time.time()))
counter -= 1
threadLock = threading.Lock()
threads = []
# 创建新线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
# 开启线程
thread1.start()
thread2.start()
# 添加线程到线程列表
threads.append(thread1)
threads.append(thread2)
print "Exiting Main Thread"
执行结果:
几点注意
- 使用锁之后,只有等thread1执行完成并释放锁之后,才执行thread2的代码
- 主线程并没有等到thread1和thread2全部执行完成后才结束,而是在运行过程中逮到机会就直接运行
3. 第三个实例使用了锁和join()函数
该实例在文章上面的方法二: 使用继承自threading.Thread的类创建线程中
小结
python解释器在执行代码时,有一个GIL锁:Global Interpreter Lock,任何python代码执行前必须获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行,GIL锁,实际给所有线程的执行代码都上了锁,因此,多线程在python中只能交替执行