线程,进程和协程
线程
线程的概念
并发
任务数大于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()
# 执行结果
0
1
2
3
4
0
1
2
3
4
0
1
2
3
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()
# 执行结果
0
0
0
1
1
1
2
2
2
3
3
3
4
4
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--由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方的锁,可能会造成死锁!