多线程
基本概念:
线程:进程中的每个子任务,不能独立存在,CPU执行的最小单位
进程:独立的所有子任务的集合
线程,进程:目的都是想同时完成任务
特点:
进程的特点:独立(内存独立,cpu使用独立)启动进程开销大(速率低),进程之间很难共享数据,和数据通信,数据安全高。
线程的特点:依赖进程(内存共享,CPU使用独立)启动开销小,线程之间共享数据容易,方便通信,线程不安全。
我们使用threading模块来运用线程
threading.currentThread(): 返回当前的线程变量。
threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
举个列子:
import threading def main(): print(threading.active_count()) #线程有几个 print(threading.enumerate())#这几个线程是哪几个 mm=threading.current_thread() #获取当前正在执行的线程 mm.setName('sssss') #给这个线程重命名为sssss print(threading.current_thread()) #现在执行的线程是哪个 if __name__=='__main__': main()
得到如下结果:
1 [<_MainThread(MainThread, started 4796)>] <_MainThread(sssss, started 4796)>得到的数据为,当前运行的线程只有一个,是这几个,名字为sssss。
除了使用方法外,线程模块同样提供了 Thread类来处理线程,Thread类提供了以下方法:
run(): 用以执行线程活动的方法。一般不使用。
start():启动线程活动使线程达到一个就绪状态。
run()和start()个人理解的区别在于,使用run()时,正在执行的对象会暂停,先执行run()的对象,执行完之后继续执行原来的对象。而使用start(),会是start对象进入执行状态,然后开始和正在执行的线程以多线程的方式运行。
join([time]):强制调用某个线程进入执行状态,本线程礼让CPU资源,进入阻塞,休眠状态,直至某个线程退出/抛异常/超时 本线程才进入就绪状态 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。isAlive(): 返回线程是否活动的。
getName(): 返回线程名。
setName(): 设置线程名。
我们来举个简单的例子:
import threading,time def T1_job(): print('T1 start\n') for i in range(10): time.sleep(0.1) print('T1 finish\n') def T2_job(): print('T2 start\n') print('T2 finish\n') def main(): T1_thread=threading.Thread(target=T1_job,name='T1') #创建一个线程 target是目标 T2_thread=threading.Thread(target=T2_job,name='T2') T1_thread.start() T2_thread.start() T1_thread.join() #join() 执行完上面的thread之后才会执行下面的程序 print('all done\n') if __name__=='__main__': main()上面这段代码因为是多线程的原因,所有结果不定, 但是由于我在print之前设定了T1_thread.join(),所以会最后执行print。
为了更好理解,我们接着看另一段代码:
#usr/bin/python #-*-coding:utf-8-*- import threading import time class Mythread(threading.Thread): #我们定义一个叫Mythread的类,注意这里的参数必须继承threading.Thread def run(self): #重写父类run方法 for i in range(10): print(self.getName(),i) time.sleep(1) if __name__=='__main__': t1= Mythread() t2=Mythread() t1.start() t2.start() for i in range(10): print(threading.current_thread().getName(), i) time.sleep(1) if i==5: t1.join()
这段代码与上段代码有个区别,就是这段代码是以类的方式写线程,而上端则以函数的方式。
上述这段代码中,一共3个线程交替共同运行,分别为,t1,t2,和运行的主线程for循环,但是在for循环中,我们添加了一个当i等于5时,t1.join(),这代表当运行到i=5时,for线程会停止运行,当t1,t2,运行完成之后才会继续运行for线程。输出结果类似:
Thread-1 0 Thread-2 0 MainThread 0 MainThread 1 Thread-1 1 Thread-2 1 MainThread 2 Thread-2 2 Thread-1 2 MainThread 3 Thread-2 3 Thread-1 3 MainThread 4 Thread-2 4 Thread-1 4 MainThread 5 Thread-2 5 Thread-1 5 Thread-1 6 Thread-2 6 Thread-2 7 Thread-1 7 Thread-1 8 Thread-2 8 Thread-1 9 Thread-2 9 MainThread 6 MainThread 7 MainThread 8 MainThread 9我们也可以在t1.join()中添加参数时间,代表优先执行多长时间,当时间过后,for会继续和t1,t2多线程运行。
上面说过多线程可能有不安全的风险,为了解决这个问题我们引用了一个锁
锁当然有锁定和未锁定两种状态,当一个线程要访问共享数据时,必须要先获得锁定,如果已经有别的线程获得锁定,那么就进入暂停状态,等别的线程把锁释放后,再进行操作。
Condition:更精确的控制锁,提供了四个方法,上锁(acquire()),等待(wait()),解锁(release()),唤醒(notify(),notify_all())
我们一般更多的会使用Condition。举个例子:
我们假设3个对象同时开始强火车票,并返回谁抢到第几张,还剩多少张:
import threading numbers=50 count1=0 lock=threading.Lock() #创建锁 cond=threading.Condition(lock=lock) #管理锁 class qiangpiao(threading.Thread): def __init__(self,name): threading.Thread.__init__(self) self.setName(name) def run(self): global numbers,count1 while True: cond.acquire() #锁定 if numbers==0: return numbers-=1 count1+=1 print('{0}抢到了{1}票,还剩{2}'.format(self.getName(),count1,numbers)) time.sleep(0.1) cond.release() #解锁 if __name__=='__main__': huangniu=qiangpiao('黄牛') mimei = qiangpiao('迷妹') zz = qiangpiao('智障') huangniu.start() #注意,创建完一个线程之后必须start mimei.start() zz.start()
在上述这段代码中,我们首先创建了一个锁lock,接着我们使用Condition管理了这个锁并命名为cond。接着我们创建了一个线程类,先初始化类,并给这个线程获得一个实参名字,接着重新定义了线程的run方法,首先引入两个全局变量numbers和count1,numbers代表的是剩余几张票,count1代表的票的张数。接下来判断numbers是否为0,是则退出程序,不是则返回我们需要的文字,即A抢到了B张票,还剩C张。
下面我们写一个小游戏:假设有一个伙夫在蒸包子,每蒸够10个就呼唤3个食客吃东西,这时伙夫休息,当三个食客将包子吃完会呼唤伙夫继续蒸包子,食客休息。代码如下:
#usr/bin/python #-*-coding:utf-8-*- import threading,time zhenglong=[] #蒸笼 #创建两把锁,蒸笼锁由伙夫掌握,另一把锁由食客掌握 cook=threading.Lock() cook_cond=threading.Condition(lock=cook) eat=threading.Lock() eat_cond=threading.Condition(lock=eat) class Cook(threading.Thread): def __init__(self): threading.Thread.__init__(self) def run(self): while True: global zhenglong eat_cond.acquire() #加锁 if len(zhenglong)==0: print('伙夫开始蒸包子') for i in range(1,11): zhenglong.append(i) print('伙夫开始蒸',i,'个包子') time.sleep(0.3) eat_cond.notify_all() #唤醒所有食客 print('厨师蒸完包子.开始呼唤吃货们醒来吃包子,厨师休眠.') eat_cond.release() #食客锁解锁 cook_cond.acquire() #蒸笼加锁 cook_cond.wait() #蒸笼等待 cook_cond.release() #蒸笼解锁 class eat(threading.Thread): def __init__(self,name): threading.Thread.__init__(self) self.getName=name def run(self): while True: eat_cond.acquire() #食客锁加锁 global zhenglong if len(zhenglong)==0: cook_cond.acquire() #蒸笼锁加锁 cook_cond.notify() #蒸笼锁唤醒 cook_cond.release() #蒸笼锁解锁 eat_cond.wait() #食客锁等待 else: foot=zhenglong.pop() if foot is not None: print('{0}吃了第{1}个包子,还剩{2}个包子'.format(self.getName,foot,len(zhenglong))) time.sleep(0.3) eat_cond.release() #食客锁解锁 if __name__=='__main__': shazi=eat('傻子') quezi=eat('瘸子') xiazi=eat('瞎子') cook_1=Cook() shazi.start() quezi.start() xiazi.start() cook_1.start() input() #形成一个无限循环
首先引用threading和time库,然后创建了两把锁,分别管理蒸笼和食客,管理蒸笼的锁由伙夫掌握,并建立了一个空列表储存包子的蒸笼。接着创建了两个线程,一个为伙夫蒸包子的线程类Cook,一个为食客吃包子的线程类eat。
在Cook中,难点在于修改之后的run中,在run中我们首先创建了一个无限while循环,引入全局变量zhenglong,接着创建了一个for循环用于伙夫蒸包子,注意的是,在伙夫蒸完包子后需唤醒所有的食客eat_cond.notify_all(),所以我们在for循环前后添加了食客锁的上锁和解锁,(Condition的锁的操作,前后必须有该锁的上锁和解锁),接着我们需要使伙夫等待,使用伙夫锁的wait,前后一样加伙夫锁的上锁和解锁。
在eat中,我们首先重新定义了参数,是name值为输入的值,接着重新run()方法,同样创建了一个一个while循环,然后引入全局变量zhenglong,当有包子时,我们定义foot为包子的个数(pop()会返回当前删除的个数),接着返回所需的内容;重点是当包子为零时,我们需要唤醒伙夫,并使食客等待,所以在if中写入Cook唤醒与eat等待,同样的在Cook唤醒前后写上加锁与解锁,而伙夫锁必须在整个while循环判断,所以我们在while循环的开始和结尾写上伙夫锁的加锁与解锁。
接着运行这个程序,完成。