1、多线程对于具有如下特点的编程任务是非常理想的:1、本质上是异步的 2、需要多个并发活动 3、每个活动的处理顺序是不确定的。
2、使用多线程编程,以及类似Queue的共享数据结构,这个编程任务可以规划成几个执行特定函数的线程。
- UserRequestThread:负责读取客户端输入,该输入可能来自I/O通道。程序将创建多个线程,每个客户端一个,客户端的请求会被放入队列中。
- RequestProcessor:该线程负责从队列中获取请求并进行处理,为第三个线程提供输出。
- ReplyThread:负责向用户输出,将结果传回给用户(如果是网络应用),或者把数据写到本地文件系统或数据库中。
使用多线程来规划这种编程任务可以降低程序的复杂性,使其实现更加清晰、高效和简洁。
3、进程
计算机程序只是存储在磁盘上的可执行二进制(或其他类型)文件。只有把它们加载到内存中并被操作系统调用,才能拥有其生命周期。进程则是一个执行中的程序。每个进程都拥有自己的地址空间、内存、数据栈以及其他用于追踪执行的辅助数据。操作系统管理其上所有进程的执行,并为这些进程合理的分配时间。进程有可以通过派生(fork或spawn)新的进程来执行其他任务,不过因为每个新进程也拥有自己的内存和数据栈等,所以只能采用进程间通信(IPC)的方式共享信息。
4、线程
与进程类似,不过它们是在同一个进程下执行的,并共享相同的上下文。可以将它们认为是在一个主进程或“主线程”中并行运行的一些“迷你进程”。
线程包括开始、执行顺序和结束三个部分。它有一个指令指针,用于记录当前运动的上下文。当其他线程运行时,它可以被抢占(中断)和临时挂起(也称为睡眠)——这种叫法叫做让步。
5、线程和Python
1、全局解释器
Python代码的执行是由Python虚拟机(又名解释器主循环)进行控制的。尽管Python解释器中可以运行多个线程,但是在任意时刻只有一个线程会被解释器执行。
对Python虚拟机的访问是由全局解释器锁(GIL)控制的。这个锁就是用来保证同时只能有一个线程运行。在多线程环境中,Python虚拟机将按照下面所述的方式执行。
- 设置GIL
- 切换进一个线程去进行
- 执行操作之一(a。指定数量的字节码指令 b。线程主动让出控制权(可以调用time.sleep()来完成))
- 把线程设置回睡眠状态(切换出线程)
- 解锁GIL
- 重复上述步骤
I/O密集型的Python程序要比计算密集型的代码能够更好的利用多线程环境。
2、不使用线程的情况
#!/usr/bin/env python from time import sleep , ctime def loop0(): print('start loop 0 at:',ctime()) sleep(4) print('loop 0 done at:',ctime()) def loop1(): print('start loop 1 at:',ctime()) sleep(2) print('loop 1 done at:',ctime()) def main(): print('starting at:',ctime()) loop0() loop1() print('all done at:',ctime()) if __name__ == '__main__': main() 结果: starting at: Fri Oct 20 12:09:34 2017 start loop 0 at: Fri Oct 20 12:09:34 2017 loop 0 done at: Fri Oct 20 12:09:38 2017 start loop 1 at: Fri Oct 20 12:09:38 2017 loop 1 done at: Fri Oct 20 12:09:40 2017 all done at: Fri Oct 20 12:09:40 2017 Process finished with exit code 0
3、Python的threading模块
threading模块提供了相比于thread更高级、功能更全面的线程管理
6、thread模块
除了派生线程外,thread模块还提供了基本的同步数据结构,称为锁对象(lock object,也叫原语锁、简单锁、互斥锁、互斥和二进制信号量)
函数/方法 | 描述 |
thread模块的函数 | |
start_new_thread | 派生一个新的线程,使用给定的args和可选的kwargs来执行function |
allocate_lock() | 分配LockType锁对象 |
exit() | 给线程退出命令 |
LockType锁对象的方法 | |
acquire(wait=None) | 尝试获取锁对象 |
locked() | 如果获取锁对象则返回True,否则,返回False |
release() | 释放锁 |
7、Threading模块
表 threading模块的对象
对象 | 描述 |
Thread | 表示一个执行线程的对象 |
Lock | 锁原语对象(和thread模块中的锁一样) |
RLock | 可重入锁对象,使单一线程可以(再次)获得已持有的锁(递归锁) |
Condition | 条件变量对象,使得一个线程等待另一个线程满足特定的条件 |
Event | 条件变量的通用版本,任意数量的线程等待某个时间的发生,在该事件后所有线程将被激活 |
Semaphore | 与线程共享的有限资源提供一个计数器,如果没有可用资源时会被阻塞。 |
Timer | 与Thread相似,不过它要在运行前等待一段时间 |
避免使用thread模块的另一个原因是该模块不支持守护进程这个概念。当主线程退出时,所有子线程都将结束,不管它们是否仍在工作。
Threading模块支持守护进程,其工作模式是:守护进程一般是一个等待客户端请求的服务器。如果没有客户端请求,守护进程就是空闲的。如果把一个线程设置为守护进程,就表示这个线程是不重要的,线程退出时不需要等待这个线程执行完成。
要将一个线程设置为守护进程,需要启动线程之前执行如下的赋值语句:thread.daemon = True
1、Thread类
表:Thread对象的属性和方法
属性 | 描述 |
Thread对象数据属性 | |
name | 线程名 |
ident | 线程的标识符 |
daemon | 布尔标志,表示这个线程是否为守护进程 |
Thread对象方法 | |
start() | 开始执行该线程 |
run() | 定义线程功能的方法 |
join(timeout=None) | 直至启动的线程终止之前一直挂起;除非给出了timeout(秒),否则会一直阻塞 |
getName() | 返回线程名 |
setName() | 设定线程名 |
isDaemon | 如果是守护进程就返回True;否则返回False |
#!/usr/bin/env python import threading from time import sleep,ctime loops = [4,2] def loop(nloop,nsec): print('start loop',nloop,'at:',ctime()) sleep(nsec) print('loop',nloop,'done at:',ctime()) def main(): print('start at:',ctime()) threads = [] nloops = range(len(loops)) for i in nloops: t = threading.Thread(target=loop,args=(i,loops[i])) threads.append(t) for i in nloops: threads[i].start() for i in nloops: #wait for all threads[i].join() #threads to finish print('all done at:',ctime()) if __name__ == '__main__': main() ================================= start at: Wed Oct 25 15:07:45 2017 start loop 0 at: Wed Oct 25 15:07:45 2017 start loop 1 at: Wed Oct 25 15:07:45 2017 loop 1 done at: Wed Oct 25 15:07:47 2017 loop 0 done at: Wed Oct 25 15:07:49 2017 all done at: Wed Oct 25 15:07:49 2017
8、锁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()
时,只有一个线程能成功地获取锁,然后继续执行代码,其他线程就继续等待直到获得锁为止。
获得锁的线程用完后一定要释放锁,否则那些苦苦等待锁的线程将永远等待下去,成为死线程。所以我们用try...finally
来确保锁一定会被释放。
锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执行,坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了。其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止。