线程、锁
一、线程
介绍:线程(Thread)也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它 可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
线程的使用:线程在程序中是独立的、并发的执行流。与分隔的进程相比,进程中线程之间的隔离程度要小,它们共享内存、文件句柄和其他进程应有的状态。线程比进程具有更高的性能,这是由于同一个进 程中的线程都有共性多个线程共享同一个进程的虚拟空间。操作系统在创建进程时,必须为该进程分配独立的内存空间,并分配大量的相关资源,但创建线程则简单得多。
-
-
-
进程之间不能共享内存,但线程之间共享内存非常容易。操作系统在创建进程时,需要为该进程重新分配系统资源,但创建线程的代价则小得多。因此,使用多线程来实现多任务并发执行比使用多进程的效率高。
-
-
二、线程的创建
1、threading模块
#导入包
import threading import time def run(n): print("task", n) time.sleep(1) print('2s') time.sleep(1) print('1s') time.sleep(1) print('0s') time.sleep(1) if __name__ == '__main__': t1 = threading.Thread(target=run, args=("t1",)) #通过进程类创建线程对象,参数与进程类比 t2 = threading.Thread(target=run, args=("t2",))
#开启线程 t1.start() t2.start() ''' task t1 task t2 2s 2s 1s 1s 0s 0s '''
2、自定义线程
import threading import time class MyThread(threading.Thread): #自己创建线程添加参数和功能,然后继承原始线程 def __init__(self, n): super(MyThread, self).__init__() # 重构run函数必须要写 self.n = n def run(self): print("我是任务:%s"%self.n) time.sleep(1) print('2s') time.sleep(1) print('1s') time.sleep(1) print('0s') time.sleep(1) if __name__ == "__main__": t1 = MyThread("t1") t2 = MyThread("t2") t1.start() t2.start() ''' 我是任务:t1 我是任务:t2 2s 2s 1s 1s 0s 0s '''
3、守护线程
使用setDaemon(True)把所有的子线程都变成了主线程的守护线程,因此当主进程结束后,子线程也会随之结束。所以当主线程结束后,整个程序就退出了。
import threading import time def run(n): print("我是任务:%s"%n) time.sleep(1) # 此时子线程停1s print('3') time.sleep(1) print('2') time.sleep(1) print('1') if __name__ == '__main__': t = threading.Thread(target=run, args=("t1",)) t.setDaemon(True) # 把子进程设置为守护线程,必须在start()之前设置 t.start() print("end") ''' 我是任务:t1 end '''
4、主进程等待子进程结束在结束,join()
import threading import time def run(n): print("我是任务:%s"%n) time.sleep(1) #此时子线程停1s print('3') time.sleep(1) print('2') time.sleep(1) print('1') if __name__ == '__main__': t = threading.Thread(target=run, args=("t1",)) t.setDaemon(True) #把子进程设置为守护线程,必须在start()之前设置 t.start() t.join() #设置主线程等待子线程结束,然后再结束 print("end") ''' 我是任务:t1 3 2 1 end '''
5、多线程共同使用全局变量
线程是进程的执行单元,进程是系统分配资源的最小单位,所以在同一个进程中的多线程是共享资源的。
import threading import time g_num = 100 def work1(): global g_num for i in range(8): g_num += 2 print("我是work1 g_num: %d" % g_num) def work2(): global g_num print("我是work2 g_num: %d" % g_num) if __name__ == '__main__': t1 = threading.Thread(target=work1) t1.start() #time.sleep(1) t2 = threading.Thread(target=work2) t2.start() ''' 我是work1 g_num: 116 我是work2 g_num: 116 '''
6、获取线程信息
用 current_thread() 方法来获取线程相关信息
import threading import time def task(): time.sleep(1) thread = threading.current_thread() print(thread) if __name__ == '__main__': for i in range(5): sub_thread = threading.Thread(target=task) sub_thread.start() #每次结果都不一样,因为线程运行是无顺序的
二、锁
介绍:
锁有两种状态——锁定和未锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为 “锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定” 状态,其他的线程才能 再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
使用:
使用 Thread 对象的 Lock 可以实现简单的进程同(如果是线程,则是 RLock),有上锁 acquire 方法和 释放 release 方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到 acquir和 release 方法之间。
1、互斥锁
指定只能让一个线程更改数据
from threading import Thread,Lock #定义全局变量num num=0 #创建一把互斥锁 mutex=Lock() def test1(): global num ''' 在两个线程中都调用上锁的方法,则这两个线程就会抢着上锁,如果有1方成功上锁,那么导致另外一方会堵塞(一直等待)直到这个锁被解开 ''' for i in range(100): mutex.acquire() # 上锁 num+=1 mutex.release() print('test1的num:%s'%num) def test2(): global num for i in range(100): mutex.acquire() # 上锁 num+=1 mutex.release() print('test2的num:%s'%num) if __name__=='__main__': t1=Thread(target=test1) t2=Thread(target=test2) t1.start() t2.start() t1.join() t2.join() ''' test1的num:100 test2的num:200 '''
2、死锁
指两个或两个以上的线程或进程在执行程序的过程中,因争夺资源而相互等待的一个现象
import time from threading import Thread,Lock import threading mutexA=threading.Lock() mutexB=threading.Lock() class MyThread1(Thread): def run(self): if mutexA.acquire(): print(self.name,'执行') time.sleep(1) if mutexB.acquire(): print(self.name,'执行') mutexB.release() mutexA.release() class MyThread2(Thread): def run(self): if mutexB.acquire(): print(self.name,'执行') time.sleep(1) if mutexA.acquire(): print(self.name,'执行') mutexA.release() mutexB.release() if __name__ == '__main__': t1=MyThread1() t2=MyThread2() t1.start() t2.start() ''' Thread-1 执行 Thread-2 执行 程序一直运行,我们看 t1.start() 那里,由于在 Mythread1 当中,会率先获取 mutexA,而在 t2.start()后,Mythread2 中会率先获取 mutexB, 此时 Mythread1 想要获取 mutexB,但是 mutexB 却已经让 t2 获得了,因此,t1 进程无法获得 muytexB。而 t2 想要获得 mutexA,但 mutexA 却已经让 t1 获得了, 还是拿不着。所以二者就一直这样互相争抢资源,导致程序无法运行下去 '''
3、递归锁
为了支持同一个线程中多次请求同一资源,Python提供了可重入锁。这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线 程所有的acquire都被release,其他的线程才能获得资源。
import time import threading A = threading.RLock() # 这里设置锁为递归锁 import threading class obj(threading.Thread): def __init__(self): super().__init__() def run(self): self.a() self.b() def a(self): # 递归锁,就是将多个锁的钥匙放到一起,要拿就全拿,要么一个都拿不到 # 以实现锁 A.acquire() print('123') print(456) time.sleep(1) print('aaaaa') A.release() def b(self): A.acquire() print('bbbbb') print('ccccc') A.release() for i in range(2): t = obj() t.start() ''' 123 456 aaaaa bbbbb ccccc 123 456 aaaaa bbbbb ccccc '''
4、全局解释锁
同一个进程中只要有一个线程获取了全局解释器(cpu)的使用权限,那么其他的线程就必须等待该线程的全局解释器(cpu)使用权消失后才能使用全局解释器(cpu),即时多个线程直接不会相互影响在同一个进程下也只有一个线程使用cpu,这样的机制称为全局解释器锁(GIL)。
import time import threading def sub(): global num num -= 1 time.sleep(1) num = 100 # 定义一个全局变量 l = [] # 定义一个空列表,用来存放所有的列表 for i in range(1,100): # for循环,此时1获取了权限,其他线程没能获取 t = threading.Thread(target=sub) #每次循环开启一个线程 t.start() # 开启线程 l.append(t) # 将线程加入列表l for i in l: i.join() # 这里加上join保证所有的线程结束后才运行下面的代码 print(num) # 输出结果为1
5、同步锁
同一时刻的一个进程下的一个线程只能使用一个cpu,要确保这个线程下的程序在一段时间内被cpu执,那么就要用到同步锁。
import time import threading R = threading.Lock() def sub(): global num R.acquire() # 加锁,保证同一时刻只有一个线程可以修改数据 num -= 1 R.release() # 修改完成就可以解锁 time.sleep(1) num = 100 # 定义一个全局变量 l = [] # 定义一个空列表,用来存放所有的列表 for i in range(1,100): # for循环 t = threading.Thread(target=sub) # 每次循环开启一个线程 t.start() # 开启线程 l.append(t) # 将线程加入列表l for i in l: i.join() # 这里加上join保证所有的线程结束后才运行下面的代码 print(num) #结果为1,原理与全局锁类似
6、信号量(BoundedSemaphore类)
互斥锁同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据
import threading import time def run(n, semaphore): semaphore.acquire() #加锁 time.sleep(1) print("run the thread:%s\n" % n) semaphore.release() #释放 if __name__ == '__main__': num = 0 semaphore = threading.BoundedSemaphore(5) # 最多允许5个线程同时运行 for i in range(22): t = threading.Thread(target=run, args=("t-%s" % i, semaphore)) t.start() while threading.active_count() != 1: pass # print threading.active_count() else: print('all threads done')
7、事件(Event类)
python线程的事件用于主线程控制其他线程的执行,事件是一个简单的线程同步对象,其主要提供以下几个方法:
- clear 将flag设置为“False”
- set 将flag设置为“True”
- is_set 判断是否设置了flag
- wait 会一直监听flag,如果没有检测到flag就一直处于阻塞状态
事件处理的机制:全局定义了一个“Flag”,当flag值为“False”,那么event.wait()就会阻塞,当flag值为“True”,那么event.wait()便不再阻塞。
#利用Event类模拟红绿灯 import threading import time event = threading.Event() def lighter(): count = 0 event.set() #初始值为绿灯 while True: if 5 < count <=10 : event.clear() # 红灯,清除标志位 print("\33[41;1mred light is on...\033[0m") elif count > 10: event.set() # 绿灯,设置标志位 count = 0 else: print("\33[42;1mgreen light is on...\033[0m") time.sleep(1) count += 1 def car(name): while True: if event.is_set(): #判断是否设置了标志位 print("[%s] running..."%name) time.sleep(1) else: print("[%s] sees red light,waiting..."%name) event.wait() #false阻塞,true不阻塞 print("[%s] green light is on,start going..."%name) light = threading.Thread(target=lighter,) light.start() car = threading.Thread(target=car,args=("MINI",)) car.start()

浙公网安备 33010602011771号