多线程
基本概念:
线程:进程中的每个子任务,不能独立存在,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循环的开始和结尾写上伙夫锁的加锁与解锁。
接着运行这个程序,完成。
本文深入探讨Python多线程的基本概念和技术细节,包括线程与进程的区别、线程的生命周期及状态转换,如何使用threading模块创建和管理线程,解决多线程中的数据安全问题。


被折叠的 条评论
为什么被折叠?



