Py之MT:Multithreaded的简介、引入、使用方法之详细攻略
目录
Multithreaded的简介
Multithreaded的引入
Multithreaded的使用方法
1、Python提供了几个用于多线程编程的模块
2、Python中使用线程有两种方式:函数或者用类来包装线程对象。
案例应用
(1)、T1、创建Thread实例,传给它一个函数
(2)、T2、创建一个Thread实例,传给它一个可调用的类对象。
Multithreaded的简介
多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理(Chip-level multithreading)或同时多线程(Simultaneous multithreading)处理器。 [1] 在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理(Multithreading)”。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程(台湾译作“执行绪”),进而提升整体处理性能。 在计算机编程中,一个基本的概念就是同时对多个任务加以控制。许多程序设计问题都要求程序能够停下手头的工作,改为处理其他一些问题,再返回主进程。可以通过多种途径达到这个目的。 1、多线程类似于同时执行多个不同程序,多线程运行有如下优点: (1)、使用线程可以把占据长时间的程序中的任务放到后台去处理。 用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度 (2)、程序的运行速度可能加快 在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。 指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存。 (1)、线程可以被抢占(中断)。 (2)、在其他线程正在运行时,线程可以暂时搁置(也称为睡眠) -- 这就是线程的退让。 2、什么是线程? 线程(亦称为轻量级进程)跟进程有些相似,不同的是:所有的线程运行在同一个进程中,共享相同的运行环境。它们可以被想象成是在主进程或“主线程”中并行运行的“迷你进程”。 线程有开始,顺序执行和结束三部分。它有一个自己的指令指针,记录自己运行到什么地方。线程的运行可能被抢占(中断)或暂时的被挂起(睡眠),让其他线程运行,这叫做让步。 一个进程中的各个线程之间共享同一片数据空间,所以线程之间可以比进程之间更方便地共享数据以及相互通讯。线程一般都是并发执行的,正是由于这种并行和数据共享的机制使得多个任务的合作变成可能。 实际上,在单CPU的系统中,真正的并发是不可能的,每个线程会被安排成每次只运行一小会,然后就把CPU让出来,让其他的线程去运行。在进程的整个运行过程中,每个线程都只做自己的事,在需要的时候跟其他的线程共享运行的结果。 当然,这样的共享并不是完全没有危险的。如果多个线程共同访问同一片数据,则由于数据访问的顺序不同,有可能导致数据结果的不一致的问题,即竞态条件(race condition)。同样,大多数线程库都带有一些列的同步原语,来控制线程的执行和数据的访问。 另一个需要注意的是由于有的函数会在完成之前阻塞住,在没有特别为多线程做修改的情况下,这种“贪婪”的函数会让CPU的时间分配有所倾斜,导致各个线程分配到的运行时间可能不尽相同,不尽公平。
Multithreaded的引入
1.为什么引入多线程编程? 在多线程(Multithreaded,MT)编程出现之前,电脑程序的运行由一个执行序列组成,执行序列按顺序在主机的中央处理器CPU中运行。即使整个程序由多个相互独立无关的子任务组成,程序都会顺序执行。 由于并行处理可以大幅度地提升整个任务的效率,故引入多线程编程。 多线程中任务具有以下特点: (1) 这些任务的本质是异步的,需要有多个并发事务; (2) 各个事务的运行顺序可以是不确定的、随机的、不可预测的。 这样的编程任务可以分成多个执行流,每个流都有一个要完成的目标。再根据不同的应用,这些子任务可能都要计算出一个中间结果,用于合并得到最后的结果。
Multithreaded的使用方法 1、Python提供了几个用于多线程编程的模块
包括thread、threading和Queue等。 (1) thread模块: 允许程序员创建和管理线程,它提供了基本的线程和锁的支持。thread模块仅仅了解就行,你应该使用更高级别的threading等。 原因之一:thread不支持守护线程 :其中thread模块需要避免的一个原因是:它不支持守护线程。当主线程退出时,所有的子线程不论它们是否还在工作,都会被强行退出。有时我们并不期望这种行为,这就引入了守护线程的概念。Threading模块支持守护线程。 原因之二:threading的Thread类是主要的运行对象。它有很多thread模块里没有的函数。
(2) threading模块: 允许程序员创建和管理线程,它提供了更高级别,更强的线程管理的功能。
Thread #表示一个线程的执行的对象
Lock #锁原语对象(跟thread模块里的锁对象相同)
RLock #可重入锁对象。使单线程可以再次获得已经获得了的锁(递归锁定)
Condition #条件变量对象能让一个线程停下来,等待其他线程满足了某个“条件”。如状态的改变或值的改变
Event #通用的条件变量。多个线程可以等待某个时间的发生,在事件发生后,所有的线程都被激活
Semaphore #为等待锁的线程提供一个类似“等候室”的结构
BoundedSemaphore #与Semaphore类似,只是它不允许超过初始值
Timer #与thread类似,只是它要等待一段时间后才开始运行
(3) Queue模块: 允许用户创建一个可用于多个线程间共享数据的队列数据结构。
2、Python中使用线程有两种方式:函数或者用类来包装线程对象。函数式:调用thread模块中的start_new_thread()函数来产生新线程。语法如下:thread.start_new_thread ( function, args[, kwargs] ) 参数说明: function - 线程函数。 args - 传递给线程函数的参数,他必须是个tuple类型。 kwargs - 可选参数。
(0.1)、没有线程的举例:使用time.sleep()函数来演示线程的工作,这个例子主要为后面线程做对比。 设计两个计时器,loop01睡眠4秒,loop02睡眠2秒,它们是在一个进程或者线程中,顺序地执行loop01()和loop02(),总运行时间为6秒。
# time.sleep()需要一个浮点型的参数,来指定“睡眠”的时间(单位秒)。这就相当于程序的运行会被挂起指定的时间。
#设计两个计时器,loop01睡眠4秒,loop02睡眠2秒,它们是在一个进程或者线程中,顺序地执行loop01()和loop02(),总运行时间为6秒。
from time import sleep, ctime
def loop01():
print ("Start loop01 at:", ctime())
sleep(4)
print ("Loop01 done at:", ctime())
def loop02():
print ("Start loop02 at:", ctime())
sleep(2)
print ("Loop02 done at:", ctime())
def main():
print ("Starting at:", ctime())
loop01()
loop02()
print ("All done at:", ctime())
if __name__ == "__main__":
main()
(0.2)、使用thread模块提供简单的额多线程机制。loop01和loop02并发地被执行,显然,短的那个先结束。总的运行时间为最慢的那个线程的运行时间,而不是所有的线程的运行时间之和。 # start_new_thread()要求一定要有前两个参数,即使运行的函数不要参数,也要传一个空的元组。 主函数中多了个sleep(6),是因为如果我们没有让主线程停下来,那主线程就会运行下一条语句,显示“All done”,然后就关闭运行着loop01和loop02的两个线程,退出了。没有写让主线程停下来等所有子线程结束后再继续运行的代码,这就是前面所说的需要同步的原因。这里采用sleep(6)作为同步机制,设置6秒=4+2秒,在主线程等待6秒后就结束了。
from time import sleep, ctime
def loop01():
print ("Start loop01 at:", ctime())
sleep(4)
print ("Loop01 done at:", ctime())
def loop02():
print ("Start loop02 at:", ctime())
sleep(2)
print ("Loop02 done at:", ctime())
def main():
try:
print ("Starting at:", ctime())
thread.start_new_thread(loop01, ())
thread.start_new_thread(loop02, ())
sleep(6)
print ("All done at:", ctime())
except Exception as e:
print("Error:",e)
finally:
print("END\n")
if __name__ == "__main__":
main()
(0.3)、线程加锁方法 有什么好的管理线程的方法呢?而不是在主线程里做个额外的延时6秒操作。因为总的运行时间并不比单线程的代码少;而且使用sleep()函数做线程的同步操作是不可靠的;如果循环的执行时间不能事先确定的话,这可能会造成主线程过早或过晚的退出。 这就需要引入锁的概念。下面代码执行loop函数,与前面代码的区别是不用为线程什么时候结束再做额外的等待了。使用锁之后,可以在两个线程都退出后,马上退出。
from time import sleep, ctime
loops = [2,4] #等待时间2、4秒。
def loop(nloop, nsec, lock): #锁序号,等待时间,锁对象
#每个线程都会被分配一个事先已经获得的锁,在sleep()的时间到了之后就释放相应的锁以通知住线程,这个线程已经结束了。
print("start loop", nloop, "at:", ctime())
sleep(nsec)
print("loop ", nloop, "done at:", ctime())
lock.release() #解锁。线程结束时需做解锁操作,调用lock.release()函数;
def main():
print("starting at:", ctime() )
locks =[]
nloops = range(len(loops)) #以loops数组创建列表并赋值给nloops
#第一个for循环创建锁:thread.allocate_lock()
for i in nloops:
lock = thread.allocate_lock() #创建锁对象,后边添加到locks列表内
lock.acquire() #获取锁对象,加锁。即分别调用各个锁的acquire()函数获得锁对象。获得锁表示“把锁锁上”,并放到锁列表locks中;
locks.append(lock) #追加到locks[]数组中
#第二个for循环创建线程:thread.start_new_thread(对应线程循环号,睡眠时间,锁)
for i in nloops:
thread.start_new_thread(loop,(i,loops[i],locks[i])) #执行多线程 (自定义的loop函数,函数参数)
#最后这个循环是一直等待(达到暂停主线程的目的):直到两个锁都被解锁才继续运行。它是顺序检查每个锁,主线程需不停地对所有锁进行检查直到都释放。
for i in nloops: #循环等待顺序检查每个所都被解锁才停止
while locks[i].locked():
pass
print("all end:", ctime() )
if __name__ == "__main__":
main()
案例应用 (1)、T1、创建Thread实例,传给它一个函数
#T1、创建Thread实例,传给它一个函数
import threading
from time import sleep, ctime
loops = [2,4] #睡眠时间
def loop(nloop, nsec):
print('Start loop', nloop, 'at:', ctime())
sleep(nsec)
print('Loop ', nloop, 'done at:', ctime())
def main():
print( 'Starting at:', ctime())
threads = [] #添加了一些Thread对象
nloops = range(len(loops)) #列表[0,1]
#创建线程:在实例化每个Thread对象时,把函数(target)和参数(args)都传进去,得到返回的Thread实例。
for i in nloops:
#实例化一个Thread调用Thread()方法与调用thread.start_new_thread()之间的最大区别是:
#新的线程不会立即开始。在你创建线程对象,但不想马上开始运行线程的时候,这是一个很有用的同步特性。
t = threading.Thread(target=loop,args=(i,loops[i]))
threads.append(t)
#开始线程:Change1:所有的线程都创建之后,再一起调用start()函数启动线程,而不是创建一个启动一个。
for i in nloops:
threads[i].start()
#等待所有结束线程:threading模块的Thread类有一个join()函数,允许主线程等待线程的结束。
#Change2:不用再管理一堆锁(分配锁、获得锁、释放锁、检查锁的状态等),只要简单地对每个线程调用join()函数就可以了。
# 使用join()比使用一个等待锁释放的无限循环清楚一些(也称“自旋锁”)。
#Tip:如果你的主线程除了等线程结束外,还有其他的事情要做(如处理或等待其他的客户请求),那就不用调用join(),只有在你要等待线程结束的时候才要调用join()。
for i in nloops:
threads[i].join()
print('All end:', ctime())
if __name__ == '__main__':
main()
这个方法,与T1方法传递一个函数很相似。但它是传一个可调用的类的实例供线程启动的时候执行,这是多线程编程的一个更为面向对象的方法。相对于一个或几个函数来说,由于类对象里可以使用类请打的功能,可以保存更多的信息,这种方法更为灵活。
#T2、创建一个Thread实例,传给它一个可调用的类对象
#coding=utf-8
import threading
from time import sleep, ctime
loops = [2,4] #睡眠时间
#创建Thread对象时会实例化一个可调用类ThreadFunc的类对象。这个类保存了函数的参数,函数本身以及函数的名字字符串
class ThreadFunc(object): #传递的是一个可调用的类,而不是一个函数
def __init__(self, func, args, name=''): #构造器__init__()函数:初始化赋值工作
self.name=name
self.func=func
self.args=args
def __call__(self): #特殊函数__call__():由于我们已经有要用的参数,所以就不用再传到Thread()构造器中;
#由于我们有一个参数的元组,这时python2要在代码中使用apply()函数即Python3中改为下边。
self.func(*self.args)
def loop(nloop, nsec):
print('Start loop', nloop, 'at:', ctime())
sleep(nsec)
print('Loop ', nloop, 'done at:', ctime())
def main():
print('Starting at:', ctime())
threads=[]
nloops = range(len(loops)) #列表[0,1]
for i in nloops:
#调用ThreadFunc类实例化的对象,创建所有线程
t = threading.Thread(
target=ThreadFunc(loop, (i,loops[i]), loop.__name__)
)
threads.append(t)
#开始线程
for i in nloops:
threads[i].start()
#等待所有结束线程
for i in nloops:
threads[i].join()
print('All end:', ctime())
if __name__ == '__main__':
main()
#Python2中的apply函数的讲解
#apply(func [, args [, kwargs ]]) 函数:用于当函数参数已经存在于一个元组或字典中时,间接地调用函数。
# args是一个包含将要提供给函数的按位置传递的参数的元组。如果省略了args,任何参数都不会被传递,
# kwargs是一个包含关键字参数的字典。
def say(a, b):
print(a, b)
apply(say,("第一个参数", "Python线程")) #输出得到:第一个参数 Python线程
参考文章Python 多线程Python线程指南