目录
2.使用Threading模块创建线程,直接从threading.Thread继承,然后重写__init__方法和run方法:
一、创建多线程
线程和进程的区别:
线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;
进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)及一些线程级的资源(如打开文件和信号等),某进程内的线程在其它进程不可见;
调度和切换:线程上下文切换比进程上下文切换要快的多
1.调用threading库的Thread类:
def test(x):
print(x)
time.sleep(2)
t1 = threading.Thread(target=test,args=(1,))
t2 = threading.Thread(target=test,args=(2,))
t1.start()
t2.start()
target是目标函数,但要注意目标函数不要带括号,如果要传入参数,用args来接受参数,并且是以元组的形式来接受。
2.使用Threading模块创建线程,直接从threading.Thread继承,然后重写__init__方法和run方法:
cLass MyThread(threading.Thread):
def__ init__ (seLf, n):
super (MyThread, seLf).__ init__()
self.n = n
def run(self):
print('以类的方式创建多线程',self.n)
time.sleep(3)
r1 = MyThread(1)
r2 = MyThread(2)
r1.start()
r2.start()
二、多线程的特性
查看活动的线程数:threading.active_count()
查看当前线程:threading.current_thread()
线程共运行多少秒程序:
import time
import threading
def run(x):
print(f"线程{x}")
time.sleep(2)
if __name__ == '__main__':
start_time = time.time()
res = []
for i in range(10):
if i == 9:
i=2
t = threading.Thread(target=run,args=(i, ))
t.start()
res.append(t)
for t in res:
t.join()
print(f"run()函数共运行了{time.time()-start_time}秒")
三、线程锁
由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,当多个线程同时修改同一条数据时可能会出现脏数据,所以出现了线程锁,即同一时刻允许一个线程执行操作。
由于线程之间是进行随机调度的,如果有多个线程同时操作一个对象,如果没有很好地保护该对象,会造成程序结果的不可预期,我们因此也称为“线程不安全”。为了防止这种情况的发生,就出现了线程锁(Lock)
def work():
global n
lock.acquire()
temp = n
time.sleep(0.1)
n = temp-1
lock.release()
if __name__ == '__main__':
lock = Lock()
n = 100
l = []
for i in range(100):
p = Thread(target=work)
l.append(p)
p.start()
for p in l:
p.join()
四、递归锁
RLcok类的用法和Lock类一样,支持嵌套,在多个锁没有释放的时候一般会使用RLock类
首先了解死锁。所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程
from threading import Thread,Lock
import time
mutexA=Lock()
mutexB=Lock()
class MyThread(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
mutexA.acquire()
print('\033[41m%s 拿到A锁\033[0m' %self.name)
mutexB.acquire()
print('\033[42m%s 拿到B锁\033[0m' %self.name)
mutexB.release()
mutexA.release()
def func2(self):
mutexB.acquire()
print('\033[43m%s 拿到B锁\033[0m' %self.name)
time.sleep(2)
mutexA.acquire()
print('\033[44m%s 拿到A锁\033[0m' %self.name)
mutexA.release()
mutexB.release()
if __name__ == '__main__':
for i in range(5):
t=MyThread()
t.start()
输出如下:
Thread-1 拿到A锁
Thread-1 拿到B锁
Thread-1 拿到B锁
Thread-2 拿到A锁
分析如上代码是如何产生死锁的:
启动5个线程,执行run方法,假如thread1首先抢到了A锁,此时thread1没有释放A锁,紧接着执行代码mutexB.acquire(),抢到了B锁,在抢B锁时候,没有其他线程与thread1争抢,因为A锁没有释放,其他线程只能等待,然后A锁就执行完func1代码,然后继续执行func2代码,与之同时,在func2中,执行代码 mutexB.acquire(),抢到了B锁,然后进入睡眠状态,在thread1执行完func1函数,释放AB锁时候,其他剩余的线程也开始抢A锁,执行func1代码,如果thread2抢到了A锁,接下来thread2要抢B锁,ok,在这个时间段,thread1已经执行func2抢到了B锁,然后在sleep(2),持有B锁没有释放,为什么没有释放,因为没有其他的线程与之争抢,他只能睡着,然后thread1握着B锁,thread2要抢B锁,ok,这样就形成了死锁
我们分析了死锁,那么python里面是如何解决这样的递归锁呢?
在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。
这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:
from threading import Thread,Lock,RLock
import time
mutexA=mutexB=RLock()
class MyThread(Thread):
def run(self):
self.f1()
self.f2()
def f1(self):
mutexA.acquire()
print('%s 拿到A锁' %self.name)
mutexB.acquire()
print('%s 拿到B锁' %self.name)
mutexB.release()
mutexA.release()
def f2(self):
mutexB.acquire()
print('%s 拿到B锁' % self.name)
time.sleep(0.1)
mutexA.acquire()
print('%s 拿到A锁' % self.name)
mutexA.release()
mutexB.release()
if __name__ == '__main__':
for i in range(5):
t=MyThread()
t.start()
输出代码如下:
Thread-1 拿到A锁
Thread-1 拿到B锁
Thread-1 拿到B锁
Thread-1 拿到A锁
Thread-2 拿到A锁
Thread-2 拿到B锁
Thread-2 拿到B锁
Thread-2 拿到A锁
Thread-4 拿到A锁
Thread-4 拿到B锁
Thread-4 拿到B锁
Thread-4 拿到A锁
Thread-3 拿到A锁
Thread-3 拿到B锁
Thread-3 拿到B锁
Thread-3 拿到A锁
Thread-5 拿到A锁
Thread-5 拿到B锁
Thread-5 拿到B锁
Thread-5 拿到A锁
Process finished with exit code 0
来解释下递归锁的代码:
由于锁A,B是同一个递归锁,thread1拿到A,B锁,counter记录了acquire的次数2次,然后在func1执行完毕,就释放递归锁,在thread1释放完递归锁,执行完func1代码,接下来会有2种可能,1、thread1在次抢到递归锁,执行func2代码 2、其他的线程抢到递归锁,去执行func1的任务代码
例2:RLcok类的用法和Lock类一样,支持嵌套,在多个锁没有释放的时候一般会使用RLock类
def func(lock):
global gl_num
lock.acquire()
gl_num += 1
time.sleep(1)
print(gl_num)
lock.release()
if __name__ == '__main__':
gl_num = 0
lock = threading.RLock()
for i in range(10):
t = threading.Thread(target=func,args=(lock,))
t.start()