一、函数式创建子线程
import threading
def foo(n):
pass
t1=threading.Thread(target=foo,args=(1,))
t1.start()
1.继承式调用(通过类去创建线程)
[root@node2 threading]# cat class-threading.sh
#!/usr/local/python3/bin/python
import threading
import time
class Mythread(threading.Thread): #定义一个类,继承threading.Thread类
def __init__(self,num): #把要传入的参数num放在init中
threading.Thread.__init__(self)
self.num = num #实例变量,参数存放在对象中
def run(self): #定义每个线程要运行函数,run是父类方法的重写,变量给run方法使用
print("running on number:%s" %self.num)
time.sleep(3)
if __name__=='__main__':
t1 = Mythread(1) #创建子线程,也就是实例化一个对象,直接调用init方法,实例化对象的参数就是方法的参数
t2 = Mythread(2) #实例对象没有调用run方法,但是内部方法一系列运行后调用
t1.start() #开启子线程
t2.start()
[root@node2 threading]# python3 class-threading.sh
running on number:1
running on number:2
2.同步锁
需求:有一个数字100,让它减1,一个函数对它减1
实现:通过多线程同时执行100次减1
[root@node2 lock]# cat lock.py
#!/usr/local/python3/bin/python
import time
import threading
def addNum(): #100的数,调用这个函数一次减1就是99.num要为0就要执行100次,但是这种操作比较慢
global num #在每个线程中都获取这个全局变量
num-=1 ##CPU执行这种时,不用到CPU切换就直接执行完,全局变量已经被修改,下一个线程取的是99,以此类推
num = 100 #设定一个共享变量,也就是全局上定义
thread_list = []
for i in range(100): #通过for循环创建100个线程
t = threading.Thread(target=addNum)
t.start()
thread_list.append(t) #把100个线程对象加入到列表中去,主要对一100个对象进行join(),不要与主线程一起跑
for t in thread_list: #等待所有线程执行完毕
t.join()
print('final num:',num)
[root@node2 lock]# python3 lock.py
final num: 0
需求:把累加的步骤拆分
问题:使用不同的格式来写变量,出现不同的结果,主要是做num-=1时,把动作插分开而导致
分析:程序执行从上到下执行,有一个100,100是一个全局的变量,100个线程可以同时操作一个变量,数据是共享的,同时起了100个线程,有先后,但是时间非常少,同样的去执行addNum函数,如第一个线程得到100后,当把temp=num,time.sleep(0.1)执行完后,可能cpu切换了,转换到另一个线程上,这时num还没有做减1的动作,CPU切换到另外一个线程上,它也是得到100,它也执行减1的动作,这两个线程都想对100进行减1,第二个线程减完后等于99,再加到另一个线程也是100-1,导致两个线程都同时做这件事,线程不安全
[root@node2 lock]# cat lock.py
#!/usr/local/python3/bin/python
import time
import threading
def addNum():
global num
temp=num ##使用temp替代num
time.sleep(0.1) ##sleep一下
num =temp-1
num = 100
thread_list = []
for i in range(100):
t = threading.Thread(target=addNum)
t.start()
thread_list.append(t)
for t in thread_list:
t.join()
print('final num:',num)
[root@node2 lock]# python3 lock.py
final num: 99
[root@node2 lock]# cat lock.py
#!/usr/local/python3/bin/python
import time
import threading
def addNum():
global num
temp=num
time.sleep(0.00001)
num =temp-1
num = 100
thread_list = []
for i in range(100):
t = threading.Thread(target=addNum)
t.start()
thread_list.append(t)
for t in thread_list:
t.join()
print('final num:',num)
[root@node2 lock]# python3 lock.py
final num: 25
问题:有时最终的结果产生不同结果,如0,1
分析:正常的情况下CPU是会切换的,结果是1,覆盖了1次,本来是0,覆盖一次就是1,主要是时间比较短,而不同的CPU也有不同的效果
[root@node2 lock]# cat lock.py
#!/usr/local/python3/bin/python
import time
import threading
def addNum():
global num
temp=num
num =temp-1
num = 100
thread_list = []
for i in range(100):
t = threading.Thread(target=addNum)
t.start()
thread_list.append(t)
for t in thread_list:
t.join()
print('final num:',num)
[root@node2 lock]# python3 lock.py
final num: 0
[root@node2 lock]# python3 lock.py
final num: 0
[root@node2 lock]# python3 lock.py
final num: 0
[root@node2 lock]# cat lock.py
#!/usr/local/python3/bin/python
import time
import threading
def addNum():
global num
temp=num
print('ok')
num =temp-1
num = 100
thread_list = []
for i in range(100):
t = threading.Thread(target=addNum)
t.start()
thread_list.append(t)
for t in thread_list:
t.join()
print('final num:',num)
[root@node2 lock]# python3 lock.py
ok #加上Print('ok')有了中间的时间,明显产生不同的结果,造成有的可以覆盖,有的不可以覆盖
final num: 14
[root@node2 lock]# python3 lock.py
ok
final num: 0
[root@node2 lock]# python3 lock.py
ok
final num: 11
中间加上时间,对于CPU来说,都是比较长的,所以切换就会覆盖掉原来的,时间短就不确定
[root@node2 lock]# cat lock.py
#!/usr/local/python3/bin/python
import time
import threading
def addNum():
global num
temp=num
time.sleep(0.2)
num =temp-1
num = 100
thread_list = []
for i in range(100):
t = threading.Thread(target=addNum)
t.start()
thread_list.append(t)
for t in thread_list:
t.join()
print('final num:',num)
[root@node2 lock]# python3 lock.py
final num: 99 #99次都覆盖
[root@node2 lock]# python3 lock.py
final num: 99
[root@node2 lock]# python3 lock.py
final num: 99
summary
(1).num-=1没问题,这是因为动作太快
(2).if sleep(1),现象会更明显,100个线程每一个一定都没执行完就进行了切换,相当于造成I/O阻塞,1s内不会切换回来,所以最后的结果一定是99
(3).使用join会把整个线程halt住,造成了串行,失去了多线程意义,只需要把计算(涉及到操作公共数据)时串行
solution: 加锁,现在使用的线程要执行完,再切换(串行)
问题:python中已经有GIL,为什么还要自行加锁
分析: 主要是功能不一样,GIL保证同一时刻在解释器中,只有一个线程在工作,对num-=1速度很快的计算操作不影响,但是对于时间长的,由于CPU的切换才导致数据问题,这时自行所加的锁就在此,当某个线程完成工作前不切换
[root@node2 lock]# cat lock.py
#!/usr/local/python3/bin/python
import time
import threading
def addNum():
global num
r.acquire() #请求锁,这里加锁只是计算这部分是串行的,其他的还可以是多线程
temp=num
time.sleep(0.0001)
num =temp-1
r.release() #释放锁
num = 100
thread_list = []
r=threading.Lock() #线程锁
for i in range(100):
t = threading.Thread(target=addNum) ##解决方法是串行的,意义造成意义不大,但是这三行代码上下可能还有其他代码,这时还是有多线程的价值
t.start() ##
thread_list.append(t) ##
for t in thread_list:
t.join()
print('final num:',num)
[root@node2 lock]# python3 lock.py
final num: 0
[root@node2 lock]# python3 lock.py
final num: 0
join解决
[root@node2 lock]# cat lock-join.py
#!/usr/local/python3/bin/python
import time
import threading
def addNum():
global num
temp=num
time.sleep(0.0001)
num =temp-1
num = 100
thread_list = []
for i in range(100):
t = threading.Thread(target=addNum)
t.start()
t.join() #####也就是t1.start()后,t2不能start,这时CPU完全是t1的,但是程序就完全是串行的,就没意义
thread_list.append(t)
for t in thread_list:
t.join()
print('final num:',num)
[root@node2 lock]# python3 lock-join.py
final num: 0
3.线程死锁和递归锁
描述:在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁,因为系统判断这部分资源都正在使用,所有这两个线程在无外力作用下将一直等待下去,下面是一个死锁的例子:
[root@node2 lock]# cat dead-recursion.py
#!/usr/local/python3/bin/python3
import threading,time
class myThread(threading.Thread):
def doA(self):
lockA.acquire() #A请求锁
print(self.name,"gotlockA",time.ctime())
time.sleep(3)
lockB.acquire() #B请求锁,但是A还没释放,在里面再加一把锁
print(self.name,"gotlockB",time.ctime())
lockB.release() #B锁先释放
lockA.release() #A锁释放
def doB(self): #操作与A相同
lockB.acquire()
print(self.name,"gotlockB",time.ctime())
#当线程1,从上到下走到这时得到lockB,其他线程是可以活动的,因为在doA最后已经释放了,线程2会来竞争,获得doA中的lockA,所以可以打印第一条,这时线程1,2各取得一把锁,当线程1从这向下走时,会请求lockA,但是lockA已经被线程2占用,而且线程2向下走时,也需要lockB,却也是被线程1所占用,造成各不相让,一直halt住,造成死锁
time.sleep(2)
lockA.acquire()
print(self.name,"gotlockA",time.ctime())
lockA.release()
lockB.release()
def run(self): #5个线程同时执行run方法
self.doA()
self.doB()
if __name__=="__main__":
lockA=threading.Lock() #两把锁
lockB=threading.Lock()
threads=[] #空列表
for i in range(5): #5个线程
threads.append(myThread()) #把实例化对象添加到空列表中
for t in threads: #循环取每个对象,第1个t就是第1个对象
t.start() #5个线程同时执行run方法
for t in threads:
t.join()
[root@node2 lock]# python3 dead-recursion.py
Thread-1 gotlockA Sun May 20 14:10:30 2018
Thread-1 gotlockB Sun May 20 14:10:33 2018
Thread-1 gotlockB Sun May 20 14:10:33 2018
Thread-2 gotlockA Sun May 20 14:10:33 2018 停住
死锁的solution:使用递归锁(可重用)
分析:锁要一层层的加,释放也要一层层的释放
问题:donA里,第一次已经请求锁了而且还没释放,为什么第二次还要请求加锁,本身第一次加锁时就只有一个线程在工作
[root@node2 lock]# cat dead-recursion.py
#!/usr/local/python3/bin/python3
import threading,time
class myThread(threading.Thread):
def doA(self):
lock.acquire() ##请求锁时,计时器做一个累加的操作
print(self.name,"gotlockA",time.ctime())
time.sleep(3)
lock.acquire() ##
print(self.name,"gotlockB",time.ctime())
lock.release() ##释放锁时,计时器进行一个递减的操作,所以不产生列锁
lock.release() ##
def doB(self):
lock.acquire() ##
print(self.name,"gotlockB",time.ctime())
time.sleep(2)
lock.acquire() ##
print(self.name,"gotlockA",time.ctime())
lock.release() ##
lock.release() ##
def run(self):
self.doA()
self.doB()
if __name__=="__main__":
lock=threading.RLock() ####内部有一个计时器和一把锁
threads=[]
for i in range(5):
threads.append(myThread())
for t in threads:
t.start()
for t in threads:
t.join()
[root@node2 lock]# python3 dead-recursion.py
Thread-1 gotlockA Sun May 20 15:40:12 2018 ##运行时,是4个的出现,说明thread1执行doA,释放完所有锁到doB的时间要比不同线程进行竞争抢资源快
Thread-1 gotlockB Sun May 20 15:40:15 2018
Thread-1 gotlockB Sun May 20 15:40:15 2018
Thread-1 gotlockA Sun May 20 15:40:17 2018
Thread-3 gotlockA Sun May 20 15:40:17 2018
Thread-3 gotlockB Sun May 20 15:40:20 2018
Thread-3 gotlockB Sun May 20 15:40:20 2018
Thread-3 gotlockA Sun May 20 15:40:22 2018
Thread-5 gotlockA Sun May 20 15:40:22 2018
Thread-5 gotlockB Sun May 20 15:40:25 2018
Thread-5 gotlockB Sun May 20 15:40:25 2018
Thread-5 gotlockA Sun May 20 15:40:27 2018
Thread-4 gotlockA Sun May 20 15:40:27 2018
Thread-4 gotlockB Sun May 20 15:40:30 2018
Thread-4 gotlockB Sun May 20 15:40:30 2018
Thread-4 gotlockA Sun May 20 15:40:32 2018
Thread-2 gotlockA Sun May 20 15:40:32 2018
Thread-2 gotlockB Sun May 20 15:40:35 2018
Thread-2 gotlockB Sun May 20 15:40:35 2018
Thread-2 gotlockA Sun May 20 15:40:37 2018
4.使用重用锁的原因
分析:一个简单的账户存还款,取款,会有这种情况转账户之类的,如果有两用户同时对一个用户进行转账户操作,就可能会产生数据安全问题,同时操作时,两个账户取的原始数都一样,导致最后操作结果有问题
繁琐的solution: 每增加一个方法对同样数据操作就加一把锁
[root@node2 lock]# cat class.py
#!/usr/local/python3/bin/python3
class Account:
def __init__(self,id,money):
self.id=id
self.balance=money
def withdraw(self,num): #取款方法
self.balance-=num
def repay(self,num): #还款方法
self.balance+=num
#def ttt(self): #如另一个线程对这方法,对同样的数据操作,这时要用同样锁对象,这里用,另一个停,这样影响是多个方法就要加多把锁
# r.acquire()
# _from.withdraw(count)
# to.repay(count)
# r.release()
def transfer(_from,to,count): #转账方法
r.acquire() #这里请求一把锁,进行一个取款,和还款的操作,还是不安全,可能还有另一个函数调用这些数据
_from.withdraw(count) #这里加锁只能控制在这个方法中只有一个线程能操作,可能另一个线程在另一个方法也在对同一数据调用
to.repay(count)
r.release()
a1=Account('reid',500)
a2=Account('lin',900)
t1=threading.Thread(target=transfer,args=(a1,a2,100,r))
t2=threading.Thread(target=transfer,args=(a2,a1,200,r))
t1.start()
t2.start()
简单优好的solution: 在类中增加锁
[root@node2 lock]# cat class.py
#!/usr/local/python3/bin/python3
class Account:
def __init__(self,id,money,r):
self.id=id
self.balance=money
def withdraw(self,num):
r.acquire() ##对操作数据本身的方法就加锁
self.balance-=num
r.release() ##
def repay(self,num):
r.acquire() ##
self.balance+=num
r.release() ##
def abc(self,num): #其他需求,同时还有方法对数据进行操作
r.acquire() #自身修改数据时,要请求锁
self.withdraw() #withdraw中还有一个请求锁
self.balance+=num
r.release()
def transfer(_from,to,count,r):
r.acquire()
_from.withdraw(count)
to.repay(count)
r.release()
r.threading.RLock()
a1=Account('reid',500)
a2=Account('lin',900)
t1=threading.Thread(target=transfer,args=(a1,a2,100,r))
t2=threading.Thread(target=transfer,args=(a2,a1,200,r))
t1.start()
t2.start()