线程,进程和协程
线程
线程的概念
并发
任务数大于cpu核载,通过系统的各种任务跳读算法,是任务“在一起”执行任务! 假的多任务
并行
任务数小于cpu核数,即任务真的在一起执行
多线程
1 同时执行
下面例子中test1和test2是同时执行
import threading import time def tes1(): for i in range(3): print("--test1--%d" % i) time.sleep(1) def tes2(): for i in range(3): print("--test2--%d" % i) time.sleep(1) if __name__ == "__main__": t1 = threading.Thread(target=test1) t2 = threading.Thread(target=test2) t1.start() # 启动线程,让线程开始执行 t2.start() # 执行结果 --test1--0 --test2--0 (间隔1秒) --test1--1 --test2--1 (间隔1秒) --test1--2 --test2--2
2 顺序执行
test1先执行,test2后执行
import threading import time def tes1(): for i in range(3): print("--test1--%d" % i) def tes2(): for i in range(3): print("--test2--%d" % i) if __name__ == "__main__": t1 = threading.Thread(target=test1) t2 = threading.Thread(target=test2) t1.start() time.sleep(1) print("test1 is over") t2.start() time.sleep(1) print("test2 is over") # 执行结果 --test1--0 --test1--1 --test1--2 (间隔1秒) test1 over --test2--0 --test2--1 --test2--2 (间隔1面) test2 over
多线程全局变量
全局变量
import threading import time g_num = 100 def test1(): global g_num g_num += 1 print("--in test1 g_num=%d" % g_num) def test2(): print("--in test2 g_num=%d" % g_num) if __name__ == "__main__": t1 = threading.Thread(target=test1) t2 = threading.Thread(target=test2) t1.start() time.sleep(1) t2.start()+ time.sleep(1)
去函数那个笔记了解全局变量
多线程全局变量实参
import threading import time g_num = 100 def test1(temp): #传递实参的方式 temp.append(33) print("--in test1 temp=%s" % str(temp)) 545. def test2(): print("--in test2 temp=%s" % str(temp)) g_num = [11,22] if __name__ == "__main__": # 创建线程 t1 = threading.Thread(target=test1, args=(g_num,)) t2 = threading.Thread(target=test2, args=(g_num,)) t1.start() time.sleep(1) t2.start() time.sleep(1)
多线程共享全局变量资源竞争
import threading import time g_num = 0 def test1(): glibal g_num for i in range(num): g_num += 1 print("--in test1 g_num=%d" % g_num) def test2(): glibal g_num for i in range(num): g_num += 1 print("--in test2 g_num=%d" % g_num) if __name__ == "__main__": # 创建线程 t1 = threading.Thread(target=test1,args=(100,)) t2 = threading.Thread(target=test2,args=(100,)) # t1 = threading.Thread(target=test1,args=(10000,)) # t2 = threading.Thread(target=test2,args=(10000,)) t1.start() t2.start()
多线程解决全局变量资源竞争01
使用互斥锁
import threading import time g_num = 0 def test1(): glibal g_num # 上锁,如果之前没有上锁,那么此时上锁成功 # 如果之前上过锁,那么就会堵塞在这里,知道这个锁解开为止 muext.acquire() for i in range(num): g_num += 1 # 解锁 muext.release() print("--in test1 g_num=%d" % g_num) def test2(): glibal g_num muext.acquire() for i in range(num): g_num += 1 muext.release() print("--in test2 g_num=%d" % g_num) # 建立一个互斥锁,默认是没有上锁的 muext = threading.Lock() if __name__ == "__main__": t1 = threading.Thread(target=test1,args=(10000,)) t2 = threading.Thread(target=test2,args=(10000,)) t1.start() t2.start() # 上述代码上锁和解锁在for循环外,解释:子线程t1和t2不知道谁先执行,假如是t1先执行,在上锁的后,一直执行for循环,知道循环结束为止然后在解锁,在执行t2此时全局变量从0变成了10000,直到for循环结束解锁变成2000,程序解锁
多线程解决全局变量资源竞争02
import threading import time g_num = 0 def test1(): glibal g_num for i in range(num): muext.acquire() g_num += 1 muext.release() print("--in test1 g_num=%d" % g_num) def test2(): glibal g_num for i in range(num): muext.acquire() g_num += 1 muext.release() print("--in test2 g_num=%d" % g_num) muext = threading.Lock() if __name__ == "__main__": t1 = threading.Thread(target=test1,args=(10000,)) t2 = threading.Thread(target=test2,args=(10000,)) t1.start() t2.start() # 如果上锁和解锁在for循环内部,不管t1和t2谁先执行,每次执行+1结束后解锁,然后在分配t1和t2谁先执行,这个先后是没有规律的,可能是t1执行很多次之后再是执行t2,可能反之,所以其中一个是for循环执行结束后得到20000,但是另外一个一定是执行10000后还在叠加
多任务版UDP聊天
import socket import threading if __name__ == "__main__": # 创建套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 绑定端口 udp_socket.bind(("", 7788)) # 获得对方ip和port dest_ip = input("请输入ip:") dest_port = input("请输入port:") # 接受数据 while True: recv_data = udp_socket.recvfrom(1024) print(recv_data) # 发送数据 while True: send_data = input("输入数据:") udp_socket.sendto(send_data.encode("utf-8"), dest_ip,dest_port) # 利用多线程 import socket import threading def recv_msg(udp_socket): # 接受数据 while True: recv_data = udp_socket.recvfrom(1024) #print(recv_data) print("[%s]:%s" %(recv_data[1], str(recv_data[0].decode("utf-8")))) # 显示发送的对方地址和信息 def send_msg(udp_socket,dest_ip,dest_port): # 发送数据 while True: send_data = input("输入数据:") udp_socket.sendto(send_data.encode("utf-8"), dest_ip,dest_port) if __name__ == "__main__": # 创建套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 绑定端口 udp_socket.bind(("", 7788)) # 获得对方ip和port dest_ip = input("请输入ip:") dest_port = int(input("请输入port:")) # 注意 t_recv = threading.Thread(target=recv_msg, args=(udp_socket,)) t_send = threading.Thread(target=send_msg, args=(udp_socket,dest_ip,dest_port)) t_recv.start() t_send.start()
进程
进程的概念
一个程序运行起来,代码+用到的资源称之为进程,他是操作系统分配资源的基本单位元
导入multiprocessing模块
子进程的传递参数
import multiprocessing # import os # 导入路径模块 import time def test(a,b,c,*args, **kwargs): print(a) print(b) print(c) print(args) # 拆包(元组) print(kwargs) # 拆包(字典) if __name__ == "__main__": p = multiprocessing.Process(target=test, args=(1,2,3,4,5,6,7,8), kwargs={"name":"Lily"}) # 当导入元素和元组的时候,统一以元组的形式导入 # 当传入kwargs的时候,在创建p类的时候,传入字典形式 p.start()
多进程之间不共享全局变量
进程之间是两个独立的程序不共享全局变量
import multiprocessing import time nums = [1,2,3] #设定全局变量 def test1(): nums.append(4) # 利用方法改变全局变量 print("在test1中nums=%s" % str(nums)) def test2(): print("在test2中nums=%s" % str(nums)) if __name__ == "__main__": # 创建进程 p1 = multiprocessing.Proess(target=test1) p2 = multiprocessing.Proess(target=test2) p1.start() p1.join() # 确保p1在执行结束后再执行p2 time.sleep(1) # 或者是在p1执行后停顿1秒 p2.start() # 得出结果是test1中[1,2,3,4], test2中[1,2,3] # 在进程中不共享全局变量
进程和线程的区别
简单的对比
进程:能够完成多任务,例如一台电脑上可以运行多个QQ
(进程是系统进行资源的分配和调度的一个独立单位)
线程:能够完成多任务,例如在同一个QQ可以开多个聊天窗口
(线程属于进程,是进程的一个实体,是cpu调度和分配的基本单位,能更小的独立运行的基本单位,线程自己使用系统资源)
区别
1 一个程序至少有一个进程,一个进程至少有一个线程
2 线程的划分尺度小于进程(就是占用资源比进程少)
3 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,提高工作效率
4 线程不能独立的运行,必须依存在进程中
5 进程就好比工厂的流水线,线程就好比流水线的员工
进程之间的通信-Queue队列
实例(仅限一台电脑或是服务器中的两个进程之间的数据共享)
import multiprocessing # 一个进程向Queue中写入数据,另一个进程从Queue获取数据 # 通过Queue完成了多歌需要配合进程间的数据共享 def down(q): # 下载数据 # 模拟在网上下载数据,就简单的创建下载好的列表 data = [1,2,3,4] # 向队列写入数据 for temp in data: q.put(temp) print("--数据已经存到队列中---") def fenxi(q): # 数据处理 fenxi_data = list() #或者是fenxi_list = [] # 向保存好的队列中获取数据 while True: new_data = q.get() # new_data是获取的数据 fenxi_data.append(new_data) # 判断:如果队被取空的话,就退出 if q.empty(): break if __name__ == "__main__": # 创建一个队列 q = multiprocessing.Queue() # 创建进程 # 传入实参队列在创建的子进程中调用q p1 = multiprocessing.Process(target=down, args=(q,)) p2 = muultiprocessing.Proess(target=fenxi, args=(q,)) p1.start() # p1.join() p2.start() # 以上操作有问题,创建的子进程p1和p2,不能确定那个子进程先运行,会导致p1还没有下载好,p2就直接获取数据,所以在p1.start()后添加一个p1.join(), p1.join()的功能就是让p1执行完之后再执行其他的进程
补充说明
初始化Queue()对象时(例如q=Queue()),有下列方法
q.put() # 向队列导入写入数据
q.get() # 向队列下载获取数据
q.empty() # 如果队列是空的,返回True, 反之False
q.full() # 如果列队满了,返回True,反之False
进程池
进程之间的通信
进程间通信就是在不同进程之间传播或交换信息,那么不同进程之间存在着什么双方都可以访问的介质呢?进程的用户空间是互相独立的,一般而言是不能互相访问的,唯一的例外是共享内存区。但是,系统空间却是“公共场所”,所以内核显然可以提供这样的条件。除此以外,那就是双方都可以访问的外设了。在这个意义上,两个进程当然也可以通过磁盘上的普通文件交换信息,或者通过“注册表”或其它数据库中的某些表项和记录交换信息。广义上这也是进程间通信的手段,但是一般都不把这算作“进程间通信”。因为那些通信手段的效率太低了,而人们对进程间通信的要求是要有一定的实时性。
进程间通信主要包括管道, 系统IPC(包括消息队列,信号量,共享存储), SOCKET.
进程和线程区别
定义的区别:进程是系统进行资源分配和调度的一个独立的单位;线程是进程的实体,是cpu调度和分配的基本单位
一个程序至少有一个进程,一个进程至少有一个线程;
线程的划分尺度小于进程(资源比进程少),使得多线程程序的并发性高。
进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率
线线程不能够独立执行,必须依存在进程中
可以将进程理解为工厂中的一条流水线,而其中的线程就是这个流水线上的工人
优缺点:线程执行开销小,效率高,,但不利于资源的管理和保护;而进程正相反。
协程
协程概念
用到更少的资源,:在一个线程中的某个函数,可以在任何地方保存当前函数的一 些临时变量等信息,然后切换到另外一个函数中执行,注意不是通过调用函 数的方式做到的,并且切换的次数以及什么时候再切换到原来的函数都由开 发者自己确定
协程和线程的区别
在实现多任务时, 线程切换从系统层面远不止保存和恢复 CPU上下文这么简 单。 操作系统为了程序运行的高效性每个线程都有自己缓存Cache等等数 据,操作系统还会帮你做这些数据的恢复操作。 所以线程的切换非常耗性 能。但是协程的切换只是单纯的操作CPU的上下,所以⼀秒钟切换个上百 万次系统都抗的住。
如何实现协程
1 生成器实现简单的协程
import time def f1(): while True: print("---1----") yield time.sleep(1) def f2(): while True: print("---2----") yield time.sleep(1) if __name__ == "__main__": ff1 = f1() ff2 = f2() while True: next(ff1) next(ff2)
2 greenlet实现协程 (了解)
from greenlet import greenlet import time def w1(): while True: print("---1---") ww1.switch() # 使用greenlet模块中swtich()方法 time.sleep(1) def w2(): while True: print("---1---") ww2.switch() # 使用greenlet模块中swtich()方法 time.sleep(1) if __name__ == "__main__": ww1 = greenlet(w1) ww2 = greenlet(w2) ww1.switch()
3 gevent实现协程(重要)
import gevent def f(n): for i in range(n): print(gevent.getcurrent(), i) g1 = gevent.spawn(f,5) g2 = gevent.spawn(f,5) g3 = gevent.spawn(f,5) g1.join() g2.join() g3.join() # 执行结果 <Greenlet "Greenlet-0" at 0x7f9e6251a748: f(5)> 0 <Greenlet "Greenlet-0" at 0x7f9e6251a748: f(5)> 1 <Greenlet "Greenlet-0" at 0x7f9e6251a748: f(5)> 2 <Greenlet "Greenlet-0" at 0x7f9e6251a748: f(5)> 3 <Greenlet "Greenlet-0" at 0x7f9e6251a748: f(5)> 4 <Greenlet "Greenlet-1" at 0x7f9e6251a948: f(5)> 0 <Greenlet "Greenlet-1" at 0x7f9e6251a948: f(5)> 1 <Greenlet "Greenlet-1" at 0x7f9e6251a948: f(5)> 2 <Greenlet "Greenlet-1" at 0x7f9e6251a948: f(5)> 3 <Greenlet "Greenlet-1" at 0x7f9e6251a948: f(5)> 4 <Greenlet "Greenlet-2" at 0x7f9e6251aa48: f(5)> 0 <Greenlet "Greenlet-2" at 0x7f9e6251aa48: f(5)> 1 <Greenlet "Greenlet-2" at 0x7f9e6251aa48: f(5)> 2 <Greenlet "Greenlet-2" at 0x7f9e6251aa48: f(5)> 3 <Greenlet "Greenlet-2" at 0x7f9e6251aa48: f(5)> 4 # 达成任务切换 import gevent def f(n): for i in range(n): print(gevent.getcurrent(), i) # 用来模拟一个耗时操作,不是用time模块 gevent.sleep(1) g1 = gevent.spawn(f,5) g2 = gevent.spawn(f,5) g3 = gevent.spawn(f,5) g1.join() g2.join() g3.join() # 执行结果 <Greenlet "Greenlet-0" at 0x7fc93b84d748: f(5)> 0 <Greenlet "Greenlet-1" at 0x7fc93b84d948: f(5)> 0 <Greenlet "Greenlet-2" at 0x7fc93b84da48: f(5)> 0 <Greenlet "Greenlet-0" at 0x7fc93b84d748: f(5)> 1 <Greenlet "Greenlet-1" at 0x7fc93b84d948: f(5)> 1 <Greenlet "Greenlet-2" at 0x7fc93b84da48: f(5)> 1 <Greenlet "Greenlet-0" at 0x7fc93b84d748: f(5)> 2 <Greenlet "Greenlet-1" at 0x7fc93b84d948: f(5)> 2 <Greenlet "Greenlet-2" at 0x7fc93b84da48: f(5)> 2 <Greenlet "Greenlet-0" at 0x7fc93b84d748: f(5)> 3 <Greenlet "Greenlet-1" at 0x7fc93b84d948: f(5)> 3 <Greenlet "Greenlet-2" at 0x7fc93b84da48: f(5)> 3 <Greenlet "Greenlet-0" at 0x7fc93b84d748: f(5)> 4 <Greenlet "Greenlet-1" at 0x7fc93b84d948: f(5)> 4 <Greenlet "Greenlet-2" at 0x7fc93b84da48: f(5)> 4
进程,线程和协程的对比(面试重点)
A 进程是资源分配的单位
B 线程是操作系统调度的单位
C 进程切换需要的资源最大,效率低
D 线程切换需要的资源一般,效率也很一般
E 协程切换任务资源小,效率高
F 多进程,多线程根据cpu的核数不一样可能并行,但是协程是在一个线程中,所以是并发的
G 进程不共享资源,线程共享资源
GIL(全局解释器锁)(面试重点)
每个线程在执行过程中都需要先获取GIL,保证同一时刻只有一个线程可以执行代码,所以线程是并发的,都是讲并发运行成串行,由此来控制同一时间内共享数据只能被一个任务修改,进而保证数据的安全!
底层知识
因为python的线程是调用操作系统的原生线程,这个原生线程就是C语言写的原生线程。因为python是用C写的,启动的时候就是调用的C语言的接口。因为启动的C语言的远程线程,那它要调这个线程去执行任务就必须知道上下文,所以python要去调C语言的接口的线程,必须要把这个上限问关系传给python,那就变成了一个在加减的时候要让程序串行才能一次计算。就是先让线程1,再让线程2.......
多线程用于IO密集型,如socket,爬虫,web
多进程用于计算密集型,如金融分析
互斥锁(面试重点)
当多个线程几乎同时修改一个共享数据的时候,需要进行同步控制,线程同步能够保证多个线程安全的竞争资源,最简单的同步机制就是引入互斥锁
如何运行:某个线程需要更改共享数据的时候,先锁定,此时资源状态为锁定状态,其他线程不能更改,直到该线程释放资源,将资源的状态变成非锁定状态,其他线程才能再次锁定该资源,互斥锁保证每次只有一个线程进行操作,从而保证多线程情况的数据正确性!
优点:确保某段关键代码只能有一个线程从头到尾完整的执行
缺点:A--阻止了多线程的并发,包含锁的某段代码只能以单线程的模式执行,效率大打折扣。B--由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方的锁,可能会造成死锁!