Day 41 线程
进程只能在同一个时间干一件事情,如果想同时干两件或者多件事情,进程就无能为力了。
进程在执行过程中如果阻塞,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行.
一是由于进程是资源的拥有者,创建和撤销和切换存在较大的时空开销,因此需要引入轻型进程,
因此在80年代,出现了能独立运行的基本单位---线程(Threads)
注意:进程是资源分配的最小单位, 线程是CPU 调度的最小单位。
每个进程中至少有一个线程。
线程与进程的区别以下区别4点
1. 地址空间和其他资源;进程间互相独立,同一进程的各线程间共享,某进程内的线程在其他进程不可见.
2 .通信,进程间通信IPC ,线程间可以直接读写进程数据段来进行通信。
3.调度和切换,线程上下文切换比进程上下文切换要快.
4. 在多线程操作系统中,进程不是一个可执行的实体.
在多线程操作系统中,通常是在一个进程中包括多个线程,每个线程都是作为cpu的基本单位,是花费最小开销的实体
线程具有以下属性:
1. 轻型实体 。
线程的实体包括程序,数据和tcb ,线程是动态概念,他的动态特性由线程控制块TCB 描述。
2. 独立调度和分派的基本单位.
3. 共享进程资源.(不需要进行IPC间通信了)
线程在同一进程中的各个线程,都可以共享该进程所有的资源,这表现在所有线程都具有相同的进程ID,这意味着线程可以访问该进程的每一个内存资源.由于同一个进程内的线程共享内存和文件,所以线程之间相互通信不必调用内核.
4 .可并发执行
在一个进程中的多个线程之间,可以并发执行,甚至运行在一个进程中所有线程都能并发执行,同样,不通进程中的线程也能并发执行,充分利用和发挥了处理机与外围并行工作的能力。
不同的进程之间是充满敌意的,彼此是抢占、竞争cpu的关系,如果迅雷会和QQ抢资源。而同一个进程是由一个程序员的程序创建,所以同一进程内的线程是合作关系,一个线程可以访问另外一个线程的内存地址,大家都是共享的。
1. 父进程有多个线程,那么开启的子线程是否需要同样多的线程
2. 在同一个进程中,如果一个线程关闭了文件,而另外一个线程正准备往该文件内写内容呢?
因此,在多线程的代码中,需要更多的心思来设计程序的逻辑、保护程序的数据。
全局解释器锁GIL
全局解释器锁-GIL 同一时刻只能有一个线程访问CPU.锁的是线程.
python解释器是python语言的问题吗 ?
是cpython解释器的特性. 这是python的一个弊病. 也是所有解释型语言的弊端.
Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然 Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。
对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。
在多线程环境中,Python 虚拟机按以下方式执行:
a、设置 GIL;
b、切换到一个线程去运行;
c、运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0));
d、把线程设置为睡眠状态;
e、解锁 GIL;
d、再次重复以上所有步骤。
在调用外部代码(如 C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,所以不会做线程切换)编写扩展的程序员可以主动解锁GIL。
thread模块不支持守护线程,当主线程退出时,所有的子线程不论它们是否还在工作,都会被强行退出。而threading模块支持守护线程,守护线程一般是一个等待客户请求的服务器,如果没有客户提出请求它就在那等着,如果设定一个线程为守护线程,就表示这个线程是不重要的,在进程退出的时候,不用等待这个线程退出。
线程的创建 Threading.Thread类
一 、创建线程的方式一
from threading import Thread import time def sayhi(name): time.sleep(2) print('%s say hello' %name) if __name__ == '__main__': t=Thread(target=sayhi,args=('egon',)) t.start() print('主线程')
输出结果:
二、创建线程的方式二
from threading import Thread import time class sayhi(Thread): def __init__(self,name): super().__init__() self.name=name def run(self): time.sleep(2) print('%s say hello' %self.name) if __name__ =='__main__': t =sayhi('egon') t.start() print('主线程')
输出结果
多线程与多进程 PID的比较
from threading import Thread from multiprocessing import Process import os def work(): print('hello',os.getpid()) if __name__ == '__main__': # part1:在主进程下开启多个线程,每个线程都跟主进程的pid一样. t1 =Thread(target=work) t2 =Thread(target=work) t1.start() t2.start() print('主线程pid',os.getpid()) print(111) #part2 开启多个进程,每个进程有不通的pid p1 =Process(target=work) p2 =Process(target=work) p1.start() p2.start() print("主进程pid",os.getpid())
输出结果:
开发效率的比较
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 '''
内存数据的共享问题
import os import time from threading import Thread def func(a,b): global g g = 0 print(g,os.getpid()) g = 100 t_lst = [] for i in range(10): t = Thread(target=func,args=(i,5)) t.start() t_lst.append(t) for t in t_lst : t.join() print(g)
输出结果
0 14444
0 14444
0 14444
0 14444
0 14444
0 14444
0 14444
0 14444
0 14444
0 14444
0
进程是最小的内存分配单位
线程是操作系统调度的最小单位
线程被cpu执行了
进程内至少含有一个线程
进程中可以开启多个线程.
开启一个线程锁需要的时间远远小于开启一个进程。
多个线程内部有自己的数据栈,数据不共享。
全局变量在多个线程之间是共享的.
线程守护进程
1 .守护进程随着主进程代码执行的结束而结束.
2.守护线程会在主线程结束之后等待其他子线程结束而结束.
import time from threading import Thread def func1(): while True: print('*'*10) time.sleep(1) def func2(): print('in func2') time.sleep(5) t = Thread(target=func1,) t.daemon = True #守护线程在主线程结束之后会随着其他子线程的结束而结束。 t.start() t2 = Thread(target=func2,) t2.start() t2.join() print('主线程')
输出结果:
********** in func2 主线程 ********** ********** ********** ********** **********
Join方法 :主线程一直等待全部的子线程结束之后,主线程自身才结束,程序退出。
join所完成的工作就是线程同步,即主线程任务结束之后,进入阻塞状态,一直等待其他的子线程执行结束之后,主线程在终止,
import time from threading import Thread def func1(): while True: print('*'*10) time.sleep(1) def func2(): print('in func2') time.sleep(5) t = Thread(target=func1,) t.daemon = True t.start() t2 = Thread(target=func2,) t2.start() t2.join() print('主线程')
输出结果
D:\PycharmProjects\test\venv\Scripts\python.exe "D:/parcharm/12/Day 30/2.py" ********** in func2 ********** ********** ********** ********** ********** 主线程
线程Lock (互斥锁) 只有一把钥匙
线程的数据是共享的所以,一般我们会上锁,而 进程一般不会上锁锁,因为数据本身是隔离的。
# noodle_lock = Lock() # fork_lock = Lock() # def eat1(name): # noodle_lock.acquire() # print('%s拿到面条啦'%name) # fork_lock.acquire() # print('%s拿到叉子了'%name) # print('%s吃面'%name) # fork_lock.release() # noodle_lock.release() # # def eat2(name): # fork_lock.acquire() # print('%s拿到叉子了'%name) # time.sleep(1) # noodle_lock.acquire() # print('%s拿到面条啦'%name) # print('%s吃面'%name) # noodle_lock.release() # fork_lock.release() # # Thread(target=eat1,args=('alex',)).start() # Thread(target=eat2,args=('Egon',)).start() # Thread(target=eat1,args=('bossjin',)).start() # Thread(target=eat2,args=('nezha',)).start()
输出结果
D:\PycharmProjects\test\venv\Scripts\python.exe "D:/parcharm/12/Day 30/2.py" alex拿到面条啦 alex拿到叉子了 alex吃面 Egon拿到叉子了 bossjin拿到面条啦
递归锁 RLOCK (递归锁.)解决死锁问题.
from threading import RLock # 递归锁 fork_lock = noodle_lock = RLock() # 一个钥匙串上的两把钥匙 def eat1(name): noodle_lock.acquire() # 一把钥匙 print('%s拿到面条啦'%name) fork_lock.acquire() print('%s拿到叉子了'%name) print('%s吃面'%name) fork_lock.release() noodle_lock.release() def eat2(name): fork_lock.acquire() print('%s拿到叉子了'%name) time.sleep(1) noodle_lock.acquire() print('%s拿到面条啦'%name) print('%s吃面'%name) noodle_lock.release() fork_lock.release() Thread(target=eat1,args=('alex',)).start() Thread(target=eat2,args=('Egon',)).start() Thread(target=eat1,args=('bossjin',)).start() Thread(target=eat2,args=('nezha',)).start()
信号量和事件Event
信号量 :同一时间允许多个线程访问一段数据。
import time
from threading import Semaphore,Thread def func(sem,a,b): sem.acquire() time.sleep(1) print(a+b) sem.release() sem = Semaphore(4) for i in range(10): t = Thread(target=func,args=(sem,i,i+5)) t.start()
输出结果:
7 9 5 11 13 15 17 19 21 23
同时输出4个数据。
事件
# 事件被创建的时候
# False状态
# wait() 阻塞
# True状态
# wait() 非阻塞
# clear 设置状态为False
# set 设置状态为True
# 数据库 - 文件夹
# 文件夹里有好多excel表格
# 1.能够更方便的对数据进行增删改查
# 2.安全访问的机制
# 起两个线程
# 第一个线程 : 连接数据库
# 等待一个信号 告诉我我们之间的网络是通的
# 连接数据库
# 第二个线程 : 检测与数据库之间的网络是否连通
# time.sleep(0,2) 2
# 将事件的状态设置为True
import time import random from threading import Thread,Event def connect_db(e): count = 0 while count < 3: e.wait(0.5) # 状态为False的时候,我只等待1s就结束 if e.is_set() == True: print('连接数据库') break else: count += 1 print('第%s次连接失败'%count) else: raise TimeoutError('数据库连接超时') def check_web(e): time.sleep(random.randint(0,3)) e.set() e = Event() t1 = Thread(target=connect_db,args=(e,)) t2 = Thread(target=check_web,args=(e,)) t1.start() t2.start()
条件condition(更为复杂的锁.)
acquire release notify wait 四种方法.
一个条件被创建初,默认有一个false
notify 和wait 必须在release 和acquire 之间。
# 条件 from threading import Condition # 条件 # 锁 # acquire release # 一个条件被创建之初 默认有一个False状态 # False状态 会影响wait一直处于等待状态 # notify(int数据类型) 造钥匙 from threading import Thread,Condition def func(con,i): con.acquire() con.wait() # 等钥匙 print('在第%s个循环里'%i) con.release() con = Condition() for i in range(10): Thread(target=func,args = (con,i)).start() while True: num = int(input('>>>')) con.acquire() con.notify(num) # 造钥匙 con.release()
定时器 Timer)
import time from threading import Timer def func(): print('时间同步') #1-3 while True: t = Timer(5,func).start() # 非阻塞的 time.sleep(5)
等待五秒执行func里的代码
Python标准模块--concurrent.futures
import time from concurrent.futures import ThreadPoolExecutor def func(n): time.sleep(2) print(n) return n*n def call_back(m): print('结果是 %s'%m.result()) tpool = ThreadPoolExecutor(max_workers=5) # 默认 不要超过cpu个数*5 for i in range(20): tpool.submit(func,i).add_done_callback(call_back) # tpool.map(func,range(20)) # 拿不到返回值 # t_lst = [] # for i in range(20): # t = tpool.submit(func,i) # t_lst.append(t) # tpool.shutdown() # close+join # # print('主线程') # for t in t_lst:print('***',t.result()) # ftp # 并发编程
队列
Queue
q =queue.Queue() 队列先进先出
import queue q=queue.Queue() q.put('first') q.put('second') q.put('third') print(q.get()) print(q.get()) print(q.get())
输出结果.
first second third
q = queue.LifoQueue() 栈 ,先进后出
import queue q=queue.LifoQueue() q.put('first') q.put('second') q.put('third') print(q.get()) print(q.get()) print(q.get()) ''' 结果(后进先出): third second first '''
优先级多列
q= queue.PriorityQueue()
import queue q =queue.PriorityQueue() q.put((20,'a')) q.put((10,'b')) q.put((30,'c')) q.put((-5,'d')) q.put((1,'?')) print(q.get())
结果:
D:\PycharmProjects\test\venv\Scripts\python.exe "D:/parcharm/12/Day 30/2.py" (-5, 'd')