本文是基于Py2.X数据库
多任务能够由多进程完成,也能够由一个进程内的多线程完成。网络
咱们前面提到了进程是由若干线程组成的,一个进程至少有一个线程。多线程
多线程相似于同时执行多个不一样程序,多线程运行有以下优势:并发
Python的标准库提供了两个模块: thread 和threading
,thread
是低级模块,threading
是高级模块,对thread
进行了封装。绝大多数状况下,咱们只须要使用threading这个高级模块。socket
# -*- coding:utf-8 -*- 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……ide
多线程和多进程最大的不一样在于,多进程中,同一个变量,各自有一份拷贝存在于每一个进程中,互不影响,而多线程中,全部变量都由全部线程共享,因此,任何一个变量均可以被任何一个线程修改,所以,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。函数
import time, threading # 假定这是你的银行存款: balance = 0 def change_it(n): # 先存后取,结果应该为0: global balance balance = balance + n balance = balance - n def run_thread(n): for i in range(100000): change_it(n) t1 = threading.Thread(target=run_thread, args=(5,)) t2 = threading.Thread(target=run_thread, args=(8,)) t1.start() t2.start() t1.join() t2.join() print balance 获得: 46, 且每次运行结果都会不同。
咱们定义了一个共享变量balance,初始值为0,而且启动两个线程,先存后取,理论上结果应该为0,可是,因为线程的调度是由操做系统决定的,当t一、t2交替执行时,只要循环次数足够多,balance的结果就不必定是0了。oop
因为彼此间的交替运算,因此结果会发生变化,若是是在银行操做,一存一取就可能致使余额不对,因此必须确保一个线程在修改balance的时候,别的线程必定不能改。ui
若是咱们要确保balance计算正确,就要给change_it()上一把锁,当某个线程开始执行change_it()时,咱们说,该线程由于得到了锁,所以其余线程不能同时执行change_it(),只能等待,直到锁被释放后,得到该锁之后才能改。因为锁只有一个,不管多少线程,同一时刻最多只有一个线程持有该锁,因此,不会形成修改的冲突。建立一个锁就是经过threading.Lock()来实现:操作系统
修改后的代码:
# -*- coding:utf-8 -*- import time, threading # 假定这是你的银行存款: balance = 0 def change_it(n): # 先存后取,结果应该为0: global balance balance = balance + n balance = balance - n lock = threading.Lock() 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,)) t2 = threading.Thread(target=run_thread, args=(8,)) t1.start() t2.start() t1.join() t2.join() print balance 结果,不管怎么执行都是0,这正是咱们指望的结果。
当多个线程同时执行lock.acquire()时,只有一个线程能成功地获取锁,而后继续执行代码,其余线程就继续等待直到得到锁为止。
得到锁的线程用完后必定要释放锁,不然那些苦苦等待锁的线程将永远等待下去,成为死线程。因此咱们用try...finally来确保锁必定会被释放。
锁的好处就是确保了某段关键代码只能由一个线程从头至尾完整地执行,坏处固然也不少,首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地降低了。其次,因为能够存在多个锁,不一样的线程持有不一样的锁,并试图获取对方持有的锁时,可能会形成死锁,致使多个线程所有挂起,既不能执行,也没法结束,只能靠操做系统强制终止。
若是你不幸拥有一个多核CPU,你确定在想,多核应该能够同时执行多个线程。
在Python的原始解释器CPython中存在着GIL(Global Interpreter Lock,全局解释器锁)所以在解释执行Python代码时,会产生互斥锁来限制线程对共享资源的访问,直到解释器遇到I/O操做或者操做次数达到必定数据时支委会释放GIL。因为全局器锁的存在,在进行多线程操做的时候,不能调用多个CPU内核,只能利用一个内核,因此在进行CPU密集型操做的时候,不推荐使用多线程,更加倾向于多进程,那么多线程适合什么样的应用场景呢?对于IO密集型操做,多线程能够明显提升效率,例如Python爬虫的开发,绝大多数时间爬虫是在等待socket返回数据,网络IO操做延时比CPU大得多。
在多线程环境下,每一个线程都有本身的数据。一个线程使用本身的局部变量比使用全局变量好,由于局部变量只有线程本身能看见,不会影响其余线程,而全局变量的修改必须加锁。
可是局部变量也有问题,就是在函数调用的时候,传递起来很麻烦:
def process_student(name): std = Student(name) # std是局部变量,可是每一个函数都要用它,所以必须传进去: do_task_1(std) do_task_2(std) def do_task_1(std): do_subtask_1(std) do_subtask_2(std) def do_task_2(std): do_subtask_2(std) do_subtask_2(std)
每一个函数一层一层调用都这么传参数那还得了?用全局变量?也不行,由于每一个线程处理不一样的Student对象,不能共享。
若是用一个全局dict存放全部的Student对象,而后以thread自身做为key得到线程对应的Student对象如何?
global_dict = {} def std_thread(name): std = Student(name) # 把std放到全局变量global_dict中: global_dict[threading.current_thread()] = std do_task_1() do_task_2() def do_task_1(): # 不传入std,而是根据当前线程查找: std = global_dict[threading.current_thread()] ... def do_task_2(): # 任何函数均可以查找出当前线程的std变量: std = global_dict[threading.current_thread()] ...
这种方式理论上是可行的,它最大的优势是消除了std对象在每层函数中的传递问题,可是,每一个函数获取std的代码有点丑。
有没有更简单的方式?
ThreadLocal应运而生,不用查找dict,ThreadLocal帮你自动作这件事:
import threading # 建立全局ThreadLocal对象: local_school = threading.local() def process_student(): print 'Hello, %s (in %s)' % (local_school.student, threading.current_thread().name) def process_thread(name): # 绑定ThreadLocal的student: local_school.student = name process_student() t1 = threading.Thread(target= process_thread, args=('Alice',), name='Thread-A') t2 = threading.Thread(target= process_thread, args=('Bob',), name='Thread-B') t1.start() t2.start() t1.join() t2.join() 获得: Hello, Alice (in Thread-A) Hello, Bob (in Thread-B)
全局变量local_school
就是一个ThreadLocal
对象,每一个Thread
对它均可以读写student
属性,但互不影响。你能够把local_school
当作全局变量,但每一个属性如local_school.student
都是线程的局部变量,能够任意读写而互不干扰,也不用管理锁的问题,ThreadLocal内部会处理。
能够理解为全局变量local_school
是一个dict,不但能够用local_school.student
,还能够绑定其余变量,如local_school.teacher
等等。
ThreadLocal
最经常使用的地方就是为每一个线程绑定一个数据库链接,HTTP请求,用户身份信息等,这样一个线程的全部调用到的处理函数均可以很是方便地访问这些资源。