python ==》 并发编程之多线程
一:什么是线程?
回答:在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程。线程:顾名思义,就是一个流水线工作的过程,一条流水线必须属于一个车间一个车间的工作过程就是一个线程,车间负责把资源整合到一起,就是一个资源单位,而一个车间内至少有一条流水线,那么流水线的工作需要电源,电源就相当于cpu。所以,进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。
总结:简单的理解为: 进程就是一个车间, 线程就是这个车间里的流水线的工作流程。
多线程:在一个进程中存在多个线程,多个控制线程共享该进程的地址空间,相当于一个车间内有多条流水线,都公用一个车间的资源。
二:线程的创建开销小:
创建进程的开销要远大于线程?
如果我们的软件是一个工厂,该工厂有多条流水线,流水线工作需要电源,电源只有一个即cpu(单核cpu)
一个车间就是一个进程,一个车间至少一条流水线(一个进程至少一个线程)
创建一个进程,就是创建一个车间(申请空间,在该空间内建至少一条流水线)
而建线程,就只是在一个车间内造一条流水线,无需申请空间,所以创建开销小
进程之间是竞争关系,线程之间是协作关系?
车间直接是竞争/抢电源的关系,竞争(不同的进程直接是竞争关系,是不同的程序员写的程序运行的,迅雷抢占其他进程的网速,360把其他进程当做病毒干死)
一个车间的不同流水线式协同工作的关系(同一个进程的线程之间是合作关系,是同一个程序写的程序内开启动,迅雷内的线程是合作关系,不会自己干自己)
三:线程与进程的区别:
1.线程的地址空间是可共享的,每个进程有自己的独立的地址空间。
2.一个进程中的线程直接接入他的进程的数据段,但是每个进程都有他们直接的从父进程拷贝过来的数据段
3.一个进程内部的线程之间能够直接通信,进程之间必须使用进程间通信实现通信
4.新的线程很容易被创建,新的进程需要从父进程复制
5.一个进程中的线程间能够有相当大的控制力度,进程仅仅只能控制他的子进程
6.改变主线程(删除,优先级改变等)可能影响这个进程中的其他线程,修改父进程不会影响子进程。
四:为何要用多线程
多线程指的是,在一个进程中开启多个线程,简单的讲:如果多个任务共用一块地址空间,那么必须在一个进程内开启多个线程。详细的讲分为4点:
1. 多线程共享一个进程的地址空间
2. 线程比进程更轻量级,线程比进程更容易创建可撤销,在许多操作系统中,创建一个线程比创建一个进程要快10-100倍,在有大量线程需要动态和快速修改时,这一特性很有用
3. 若多个线程都是cpu密集型的,那么并不能获得性能上的增强,但是如果存在大量的计算和大量的I/O处理,拥有多个线程允许这些活动彼此重叠运行,从而会加快程序执行的速度。
4. 在多cpu系统中,为了最大限度的利用多核,可以开启多个线程,比开进程开销要小的多。(这一条并不适用于python)
五:线程中相关的模块内容介绍。
1.threading 模块介绍:
multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性,因而不再详细介绍
2.开启线程的两种方式:
直接调用 And 继承调用
直接调用: from threading import Thread import os def talk(): print('%s is running'%os.getpid()) if __name__ == '__main__': t = Thread(target = talk) t.start() print('主') 总结:Thread 开启线程,主程序后执行的。 输出结果: 8960 is running 主 Process finished with exit code 0 继承调用: from threading import Thread import os class Mythread(Thread): def __init__(self): super().__init__() self.name= name def run(self): print('pid:%s name:[%s] is running'%(os.getpid(),self.name)) if __name__ =='__main__': t = Mythread('aray) t.start() print('主',os.getpid()) 输出结果: pid:15648 name:[aray]is running 主 15648 Process finished with exit code 0
3.在一个进程下开启多个线程与在一个进程下开启多个子进程的区别:
from threading import Thread from multiprocessing import Process import os def work(): print('hello') if __name__ == '__main__': #在主进程下开启线程 t=Thread(target=work) t.start() print('主线程/主进程') ''' 打印结果: hello 主线程/主进程 ''' #在主进程下开启子进程 t=Process(target=work) t.start() print('主线程/主进程') ''' 打印结果: 主线程/主进程 hello '''
from threading import Thread from multiprocessing import Process import os def work(): global n n=0 if __name__ == '__main__': # n=100 # p=Process(target=work) # p.start() # p.join() # print('主',n) #毫无疑问子进程p已经将自己的全局的n改成了0,但改的仅仅是它自己的,查看父进程的n仍然为100 n=1 t=Thread(target=work) t.start() t.join() print('主',n) #查看结果为0,因为同一进程内的线程之间共享进程内的数据
4.练习:模拟一个文编编辑器。就简单做一个大写化处理,并保存,写入文件。
from threading import Thread import os msg_l = [] format_l = [] def Listen(): '''监听输入''' while True: #循环,不至于输入一次内容就关闭页面 msg = input('>>:').strip() if not msg: continue msg_l.append(msg) #把要输入的内容,添加到空列表里。 def Format(): '''格式化输出''' while True: if msg_l: #判断,只有msg_l 不为空,才往下执行. 就是要传数据上来。 res = msg_l.pop() #pop 队列输出 format_l.append(res.upper()) #格式化之后的结果,添加到另一个新的空列表。 def Hold(): ''' 保存''' while True: if format_l: #判断,如果有个内容,为True,才往下执行。 with open('db.txt','a',encoding = 'utf-8') as f: res = format_l.pop() f.write('%s \n'res) if __name__ == '__main__': t1 = Thread(target = Listen) t2 = Thread(target = Format) t3 = Thread(target = Hold) t1.start() t2.start() t3.start() 输出结果: >>: a >>: b >>: c 这时,会把abc 以大写的形式,写入 db.txt 文件里面。
5.线程的其他方法:
isAlive(): 返回线程是否活动的
getname(): 返回线程名
setname():设置线程名
threading.currentThread(): 返回当前的线程变量
threading.enumerate(): 返回一个包含正在运行的线程list,正在运行指线程启动后,结束前,不包括启动前和终止后的线程。
threading.activeCount(): 返回正在运行的线程数量,与 len(threading.enumerate())有相同的结果。
from threading import Thread,currentThread,activeCount import os,time,threading def talk(): print('%s is running '%currentThread().getName()) print(t.is_alive()) if __name__ == '__main__': t = Thread(target=talk,name='aray') t.start() time.sleep(1) print(t.is_alive()) print(threading.activeCount()) print(len(threading.enumerate())) print('主',activeCount()) 输出结果: aray is running True False 1 1 主 1 Process finished with exit code 0
5.1:主线程等待机制:join
from threading import Tread,currentTread import time def talk(): time.sleep(2) print('%s is running '%currentThread().getName()) if __name__ == '__main__': t1 = Thread(target = talk,name='aray') t1.start() print('主‘) 注意:没有join 之前。 输出结果: 主 aray is running Process finished with exit code 0 #加了join之后。 from threading import Tread,currentTread import time def talk(): time.sleep(2) print('%s is running '%currentThread().getName()) if __name__ == '__main__': t1 = Thread(target = talk,name='aray') t1.start() t1.join() print('主‘) 输出结果: aray is running 主 Process finished with exit code 0
5.2:守护线程:daemon
from threading import Thread,currentThread,activeCount from multiprocessing import Process import os,time,threading def talk(): time.sleep(2) print('%s is running '%currentThread().getName()) # print('%s is running '%os.getpid()) if __name__ == '__main__': t2 = Process(target = talk) t3 = Process(target = talk) t4 = Process(target = talk) t2.daemon = True t3.daemon = True t4.daemon = True t2.start() t3.start() t4.start() print('主',os.getpid()) 输出结果: 主 14576 Process finished with exit code 0
5.3:全局解释器锁
import time from threading import Thread,Lock n = 100 def work(): mutex.acquire() global n temp = n time.sleep(0.5) n = temp -1 mutex.release() if __name__ =='__main__': mutex = Lock() t_l = [] for i in range(100): t = Thread(target = work) t_l.append(t) t.start() for t in t_l: t.join() print(n) 输出结果: 0 Process finished with exit code 0
5.4:详解join
1.没有加锁 from threading import Thread,Lock import time n = 100 def work(): global n temp = n time.sleep(0.1) n =temp -1 if __name__ == '__main__': mutex = Lock() start = time.time() for i in range(100): t = Thread(target=work) t.start() t.join() stop = time.time() print("%s time: %s"%(n,stop-start)) 输出结果: 0 time: 10.085442781448364 Process finished with exit code 0 2.加锁解决并发,竞争的问题。保证安全,但是所处理的时间也相应增加。 n = 100 def work(): # time.sleep(2) global n mutex.acquire() temp = n time.sleep(0.2) n = temp -1 mutex.release() if __name__ == '__main__': mutex = Lock() l_t =[] start =time.time() for i in range(100): t = Thread(target=work) l_t.append(t) t.start() for t in l_t: t.join() stop = time.time() print("%s time: %s"%(n,stop-start)) 输出结果: 0 time: 20.061145067214966 Process finished with exit code 0 3.用join解决并发,竞争的问题。 需要21s n = 10 def work(): time.sleep(2) #(注意join是全局的包括这里的2s) global n temp = n time.sleep(0.1) n = temp -1 if __name__ == '__main__': mutex = Lock() l_t =[] start =time.time() for i in range(10): t = Thread(target=work) l_t.append(t) t.start() t.join() stop = time.time() print("%s time: %s"%(n,stop-start)) 输出结果: 0 time: 21.011882066726685 Process finished with exit code 0 4.用锁来解决并发,竞争的问题。 n = 10 def work(): time.sleep(2) global n mutex.acquire() temp = n time.sleep(0.1) n = temp -1 mutex.release() if __name__ == '__main__': mutex = Lock() l_t =[] start =time.time() for i in range(10): t = Thread(target=work) l_t.append(t) t.start() for t in l_t: t.join() stop = time.time() print("%s time: %s"%(n,stop-start)) #只需要3s 输出结果: 0 time: 3.0060813426971436 Process finished with exit code 0
小结:
互斥锁是 锁 局部的. 对共享数据修改的那一部分。
join 是 锁 全局的.
5.5:多线程 and 多进程
多进程:
优点:可以利用多核
缺点:开销大
多线程:
优点:开销小
缺点:不可利用多核
from multiprocessing import Process from threading import Thread import time def work(): res = 0 for i in range (10000000): res +=1 if __name__ == '__main__': l = [] start = time.time() for i in range(4): p = Process(target=work) l.append(p) p.start() for p in l: p.join() stop = time.time() print('%s'%(stop-start)) 输出结果: 0.9400010108947754 Process finished with exit code 0
from multiprocessing import Process from threading import Thread import time def work2(): time.sleep(2) if __name__ == '__main__': l = [] start = time.time() for i in range(400): p2 = Thread(target=work2) # p2 = Process(target=work2) l.append(p2) p2.start() for p2 in l: p2.join() stop = time.time() print('%s'%(stop-start)) 输出结果: 2.048475742340088 Process finished with exit code 0
多进程主要用于计算功能。
多线程主要用户I/O机制中。
5.6:死锁的场景
from threading import Thread,Lock,currentThread mutexA =Lock() mutexB =Lock() 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(1) #线程2 等 B 锁, 线程1 等 A锁 这样就形成死锁了。这里sleep 1 是为了保证线程2 拿到A锁 mutexA.acquire() print('%s 拿到A 锁' % self.name) mutexA.release() mutexB.release() if __name__ == '__main__': for i in range(10): t = Mythread() t.start() 输出结果: Thread-1 拿到 A锁 Thread-1 拿到B 锁 Thread-1 拿到 B锁 Thread-2 拿到 A锁 注意:这里是卡主了,没有执行完毕。
5.7:递归锁:用来解决死锁的问题。(RLock)
from threading import Lock,Thread,RLock import time mutexB=mutexA=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(1) #线程2 等 B 锁, 线程1 等 A锁 这样就形成死锁了。这里sleep 1 是为了保证线程2 拿到A锁 mutexA.acquire() print('%s 拿到A 锁' % self.name) mutexA.release() mutexB.release() if __name__ == '__main__': for i in range(10): 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-6 拿到 A锁 Thread-6 拿到B 锁 Thread-6 拿到 B锁 Thread-6 拿到A 锁 Thread-8 拿到 A锁 Thread-8 拿到B 锁 Thread-8 拿到 B锁 Thread-8 拿到A 锁 Thread-10 拿到 A锁 Thread-10 拿到B 锁 Thread-10 拿到 B锁 Thread-10 拿到A 锁 Thread-5 拿到 A锁 Thread-5 拿到B 锁 Thread-5 拿到 B锁 Thread-5 拿到A 锁 Thread-9 拿到 A锁 Thread-9 拿到B 锁 Thread-9 拿到 B锁 Thread-9 拿到A 锁 Thread-7 拿到 A锁 Thread-7 拿到B 锁 Thread-7 拿到 B锁 Thread-7 拿到A 锁 Thread-3 拿到 A锁 Thread-3 拿到B 锁 Thread-3 拿到 B锁 Thread-3 拿到A 锁 Process finished with exit code 0
5.8:信号量: 也是一把锁 举个例子: 公厕 (Semaphore)
from threading import Thread,Semaphore,currentThread import time,random sm = Semaphore(5) def task(): sm.acquire() print('%s 小号'%currentThread().getName()) time.sleep(random.randint(1,10)) print('%s over'%currentThread().getName()) sm.release() if __name__ == '__main__': for i in range(6): t = Thread(target=task) t.start() 输出结果: Thread-1 小号 Thread-2 小号 Thread-3 小号 Thread-4 小号 Thread-5 小号 Thread-2 over Thread-6 小号 Thread-4 over Thread-6 over Thread-5 over Thread-3 over Thread-1 over Process finished with exit code 0
5.9:Event:同进程一样。
event.isset() 返回event的状态值 event.wait() 如果 event.isset() == False 将 阻塞线程 event.set() 设置event的状态值为True,所有阻塞池的线程进入就绪状态,等待调度 event.clear() 回复event的状态值为false
红绿灯例子:
from threading import Event,Thread,currentThread import time e = Event() def traffic_lights(): time.sleep(2) e.set() def car(): print('% s 等待'%currentThread().getName()) e.wait() print('%s 行驶'%currentThread().getName()) if __name__ == '__main__': for i in range(10): t = Thread(target=car) t.start() traffic_thread = Thread(target=traffic_lights) traffic_thread.start() 输出结果: Thread-1 小号 Thread-2 小号 Thread-3 小号 Thread-4 小号 Thread-5 小号 Thread-7 等待 Thread-8 等待 Thread-9 等待 Thread-10 等待 Thread-11 等待 Thread-12 等待 Thread-13 等待 Thread-14 等待 Thread-15 等待 Thread-16 等待 Thread-5 over Thread-6 小号 Thread-13 行驶 Thread-11 行驶 Thread-10 行驶 Thread-16 行驶 Thread-9 行驶 Thread-14 行驶 Thread-12 行驶 Thread-15 行驶 Thread-7 行驶 Thread-8 行驶 Thread-4 over Thread-1 over Thread-3 over Thread-2 over Thread-6 over Process finished with exit code 0
检测链接例子:
from threading import Event,Thread,currentThread import time e = Event() def conn_mysql(): count = 1 while not e.is_set(): if count > 3: raise ConnectionError('尝试链接的次数过多') print('第%s次尝试'%currentThread().getName()) e.wait(1) count+=1 print('%s开始链接'%currentThread().getName()) def check_mysql(): print('%s检测mysql'%currentThread().getName()) time.sleep(2) e.set() if __name__ == '__main__': for i in range(2): t = Thread(target=conn_mysql) t.start() t = Thread(target=check_mysql) t.start() 输出结果: 第Thread-1次尝试 第Thread-2次尝试 Thread-3检测mysql 第Thread-1次尝试 第Thread-2次尝试 第Thread-1次尝试 第Thread-2次尝试 Thread-1开始链接 Thread-2开始链接 Process finished with exit code 0
6.0:定时器:
from threading import Timer def hello(n): print('hello,world',n) t=Timer(3, hello, args=(123,)) t.start() 输出结果:等待3s hello,world 123 Process finished with exit code 0
6.1:线程Queue:
import queue q= queue.Queue(4) #先进先出 q.put('first') q.put('second') q.put('third') q.put('fourth') print(q.get()) print(q.get()) print(q.get()) print(q.get()) 输出结果: first second third fourth Process finished with exit code 0
import queue q = queue.LifoQueue() #先进后出 q.put('first') q.put('second') q.put('third') q.put('fourth') print(q.get()) print(q.get()) print(q.get()) print(q.get()) 输出结果: fourth third second first Process finished with exit code 0
import queue q = queue.PriorityQueue() q.put((20,'a')) q.put((10,'b')) q.put((30,'c')) print(q.get()) print(q.get()) print(q.get()) 输出结果: (10, 'b') (20, 'a') (30, 'c') Process finished with exit code 0