python语法基础-并发编程-线程-长期维护
Posted 技术改变命运Andy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python语法基础-并发编程-线程-长期维护相关的知识,希望对你有一定的参考价值。
############### 线程和GIL,全局解释器锁 ##############
"""
线程
为什么会有进程?
主要是能够同时处理多个任务,多个任务还要进行切换,时间片轮转
为什么会有线程?
进程并不是执行任务的最小单元,每一个进程里面有都一个线程,我们叫做主线程,
早期没有线程,一个进程只能干一个任务,如果有多个任务,只能多起进程,进程太多不行的,
进程内部的内存是共享的,所以需要线程,否则还要涉及到进程间的通信,这比较浪费资源
所以线程的出现解决了两个问题:
1,进程内部的通信
2,一个进程可以处理多个任务,
线程的开销比进程少,可以认为是一个轻型的进程,
进程可以任务是车间,线程可以认为是每一个工人,
进程是资源分配的最小单位,线程是cpu调度的最小单位,
###########################
进程和线程的区别:
1,进程之间的内存是独立的,但是一个进程之内的线程是可以共享的,
2,进程之间切换是慢于线程之间的切换的,
"""
# 第一个例子
from threading import Thread
import time,os
# 多线程并发
# def func(n): # 这是子线程完成的
# time.sleep(1) # 虽然是打印了10次,但是只等待了1秒,所以10线程之间是并发的,
# print(n)
#
# for i in range(10): # 启动10个线程,
# t = Thread(target=func,args=(1,)) # 注册
# t.start() # 这是启动了一个线程
# 第二种启动线程的方法:
# class MyTread(Thread):
#
# def __init__(self,arg):
# super().__init__()
# self.arg=arg
# def run(self):
# time.sleep(1)
# print(1,os.getpid())
#
#
# for i in range(10):
# t = MyTread(10) # 传递参数
# t.start()
#
# print("主线程",os.getpid()) # 打印子进程和主进程的进程号,都是一样的
# 进程里面存了导入的模块,文件所在的位置,内置的函数代码
# 线程里面存了少量的必不可少代码,比如做加法运算,需要的参数,
# 所以目前看起来进程和线程的启动是非常的类似的,
#########################################
# 同一个数据,多个线程去操作,也会出现问题,所以也有线程锁的概念,
# 这种机制叫做全局解释器锁,英文GIL,
# 这种机制的结果就是:同一时间只有一个线程去访问cpu,
# 你想要访问数据,就必须拿到这个钥匙,
# 这个锁,所的是线程,不是锁的某一个数据,
# 这样不好,因为同一时间cpu上面只有一个线程,这样不能充分的利用cpu
# 这不是Python语言的问题,这是cPython解释器的问题,如果你有一个jPython解释器就行
# 这的确是一个弊病,导致Python的多线程形同虚设,
# 那么为什么不解决呢?
# java和c++都是编译性语言,Python是一个解释性语言,php也是,
# 目前解释性语言就是存在这个问题,这个矛盾还不可调和,
# 是否把数据加锁就可以了,也是不行的,数据太多了,这么大范围的加锁,最终导致的效率,还不如全局解释器锁的性能好,
# 在cPython解释器下的Python程序,在同一时间,多个线程只能有一个线程在cpu执行,
# 这不意味着多线程就没有意义了,
# 因为只有涉及到计算的地方才会使用到CPU,
# 高CPU:所以在计算类的高cpu利用率的,Python不占优势,
# 高IO:我们写的代码很多都涉及到这种,
# 比如qq聊天,处理日志文件,爬取网页,处理web请求,读写数据库,都是高io的,都是有Python用武之地的,
# 所以Python不能处理计算类高的问题,这不影响他在web编程的作用,
# 如果你真的需要高并发呢,你可以使用多进程,就不会有GIL锁了,
#######################################
# Threading中的其他方法
import threading
def func(n):
print(n,threading.current_thread()) # <Thread(Thread-1, started 5428)>
for i in range(10):
threading.Thread(target=func,args=(1,)).start()
print(threading.active_count()) # 查看活跃的线程数,
print(threading.current_thread())
############### 守护线程 ##############
# 守护线程:
from threading import Thread
import time
def func1(name):
while True:
print(11111111)
time.sleep(1)
def func2(name):
print(2222222)
time.sleep(5)
if __name__ == ‘__main__‘:
t=Thread(target=func1,args=(‘andy‘,))
t.daemon = True # 主线程代码结束,子线程随之结束,
# 不加守护线程,主线程就会等待子线程的结束,然后主线程才会结束,
t.start()
t2=Thread(target=func2,args=(‘lucy‘,))
t2.start()
# 主线程会等待子线程结束
print(‘主线程‘)
print(t.is_alive())
‘‘‘
主线程
True
‘‘‘
# 所以进程的守护进程,和线程的守护进程是有不一样的点的
# 1.对主进程来说,运行完毕指的是主进程代码运行完毕
# 2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕
# 这是一个盲点,后续需要再看看,
############### 线程锁---互斥锁,死锁,递归锁, ##############
# 线程锁
# 你用进程锁的时候不多,线程锁用的多
# 为什么会用到线程锁?
# 就是因为多个线程同时操作一个资源会出问题,
# 一般的锁,lock,叫做互斥锁,就是说你只能拿到你把钥匙,
# 死锁
# 递归锁,你拿到一把钥匙,就拿到了这个钥匙串,别的线程就拿到不到这个钥匙串了,
from threading import Lock,Thread
import time
# 模拟多线程出错的场景,
# 全局的n是10,然后创建10个线程,去执行n-1的操作,
# 结果按理说应该是0,但是结果是9,
# 为什么?
# 10个线程去拿n=10,所有人拿的都是10,然后都同步减去1,然后返回都是9,所以是9,
# 但是有GIL锁啊,为什么还会产生这种情况?
# 还是因为时间片轮转,你取到了数据,但是还没有来得及减1,这个值就被其他的线程拿走了,还是10,
#
# def func():
# global n
# temp = n
# time.sleep(0.2)
# n= temp-1
#
# n = 10
# t_list=[]
# for i in range(10):
# t= Thread(target=func)
# t.start()
# t_list.append(t)
#
#
# for i in t_list:t.join()
# print(n)
# 模拟线程加锁的情况,然后看看结果
# 这样加了锁之后,结果应该就是0了,是的,结果是0了
# 加锁降低了性能,但是保证了数据的安全性,
# def func(lock):
# global n
# lock.acquire()
# temp = n
# time.sleep(0.2)
# n= temp-1
# lock.release()
#
# n = 10
# t_list=[]
# lock = Lock()
#
# for i in range(10):
# t= Thread(target=func,args=(lock,))
# t.start()
# t_list.append(t)
#
#
# for i in t_list:t.join()
# print(n)
# 现在看看科学家吃面的问题,看看死锁的场景
# 这个例子是只有拿到面和叉子,才可以吃面,
#
#
# noodle_lock = Lock()
# fork_lock = Lock()
# def eat1(name):
# noodle_lock.acquire()
# print("%s拿到面条"%name)
# fork_lock.acquire()
# print("%s拿到叉子"%name)
# print("吃面")
# 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("吃面")
# noodle_lock.release()
# fork_lock.release()
#
# Thread(target=eat1,args=("name1",)).start()
# Thread(target=eat2,args=("name2",)).start()
# Thread(target=eat1,args=("name3",)).start()
# Thread(target=eat1,args=("name4",)).start()
#
# """
# name1拿到面条
# name1拿到叉子
# 吃面
# name2拿到叉子
# name3拿到面条
# 这种情况不同的人拿到了叉子和面条,就无法进行了,就阻塞了,就死锁了,
# 怎么解决?有一个递归锁
# """
# 递归锁,来解决死锁的问题
# # 你可以看做是钥匙串上面的两把钥匙,一旦你拿到了一把钥匙,就证明你拿到了整个钥匙串了,
from threading import RLock
noodle_lock = fork_lock = RLock() # 你可以看做是钥匙串上面的两把钥匙
def eat1(name):
noodle_lock.acquire()
print("%s拿到面条"%name)
fork_lock.acquire()
print("%s拿到叉子"%name)
print("吃面")
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("吃面")
noodle_lock.release()
fork_lock.release()
Thread(target=eat1,args=("name1",)).start()
Thread(target=eat2,args=("name2",)).start()
Thread(target=eat1,args=("name3",)).start()
Thread(target=eat2,args=("name4",)).start()
############### 信号量和事件 ##############
# 信号量
# 信号量就是控制只能有n个线程能访问这段代码
# from threading import Semaphore,Thread
# import time
# 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()
# 事件:
# 事件被创建的时候是false状态,这个false状态会导致wait被阻塞,
# true状态的时候,wait就不阻塞了
# clear,设置为false
# set,设置状态未false,
# 上次举例的是红绿灯的例子,
# 现在我们举一个例子,检测数据库的可连接情况
# 启动两个线程,
# 第一个线程连接数据库,
# 等待一个信号,告诉我们之间的网络是通的
# 第二个线程,检测和数据自己之间的网络是否是通的
# 通了之后把事件的状态改为true,
import time,random
from threading import Thread,Event
def connect_db(e):
count = 0
while count < 3:
# e.wait() # 这个是一直等待,这种比较浪费资源,
e.wait(0.1) # 这是我只等待1秒,否则我就不等你了,# 但是我应该有一个重连的过程,比如三次,三次都连接不上,就断掉了
if e.is_set() == True:
print("连接数据库")
break
else:
print("连接失败")
count += 1
else:
raise TimeoutError("数据库连接超时") # 主动报异常
def check_web(e):
time.sleep(random.randint(0,2))
e.set()
e = Event()
t1 = Thread(target=connect_db,args=(e,))
t2 = Thread(target=check_web,args=(e,))
t1.start()
t2.start()
############### 线程的条件和定时器 ##############
# 条件
from threading import Condition,Thread,Timer
# 可以把条件视为一个更加复杂的锁
# 同时也有两个方法,acquire,release
# 一个条件被创建,默认也是一个false状态,会影响wait处于等待状态
# notify(int),这是制造钥匙,参数是制造多少把钥匙
# 这个几乎就是只会出现在面试里面,平时基本不会用到,
#
# 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()
# 定时器:
def func():
print("时间同步")
Timer(2,func).start() # 等待两秒钟开启一个线程,
############### 线程,队列 ##############
# 线程,队列
# 加锁会自己写代码,不方便,我们可以使用队列
# 队列内置了很多的锁,可以保证数据安全,
import queue
q = queue.Queue()
q.put()
q.put_nowait() # 这个会报错
q.get()
q.get_nowait() # 这个会报错,
# 队列的特点:先进先出,
queue.LifoQueue() # 栈,先进后出,
q.put(1)
q.put(2)
q.put(3)
print(q.get())
print(q.get())
print(q.get())
queue.PriorityQueue() # 优先级队列,
# 放数据的视乎,除了数据,还需要一个参数:优先级
q.put(20,1)
q.put(10,2) # 数字越小,优先级越高,优先级一样,就是按照和ASk码排,
q.put(30,3)
print(q.get())
print(q.get())
print(q.get())
# 总结:
# 1,普通队列
# 2,栈
# 3,优先级队列,
# 这三种都不会出现多线程抢占资源,
############### 线程池 ##############
# 线程池
# 新的一个模块,
# 1 介绍
# concurrent.futures模块提供了高度封装的异步调用接口
# ThreadPoolExecutor:线程池,提供异步调用
# ProcessPoolExecutor: 进程池,提供异步调用
from concurrent.futures import ThreadPoolExecutor
import time
def func(n):
time.sleep(2)
print(n)
return n*n
tpool = ThreadPoolExecutor(max_workers=5)
# 进程池,启动cpu核数+1.
# 而线程池的启动是cpu核数 * 5 不要超过这个,
t_list = []
for i in range(20):
t = tpool.submit(func,i) # 提交一个任务,传递一个参数,
t_list.append(t)
tpool.shutdown()
# shutdown做了两个事情:
# 1,colse 关闭这个池子,不让有任务进来,
# 2,join是阻塞,直到这个池子的任务执行完,
# 所以是一个shutdown做了两个事情,
print("主进程")
for t in t_list:print(t.result())
############### 进程和线程的对比 ##############
进程和线程的对比:
面试的时候会问到,
进程是很多资源的总称,包括代码,包括显示器,包括鼠标,键盘等
线程是轻型的实现多任务的方式,
qq程序的多开,就是多个进程,
一个进程里面可以多个任务,这就是线程,
----------------------------
进程先有的,然后才有线程,
进程是一个资源分配的单位,具体谁拿着资源去做的呢,是线程,
一个进程里面一定有一个主线程,
--------------
所以多线程能实现多任务,是指的在一个进程里面有多个线程,实现多任务,
多进程的多任务,是有开辟了一个资源分配的单位,实现多任务,这个时候一个进程里面可能只有一个主线程,
真正执行代码的时候还是线程,
-------------
所以进程需要很大的资源才可以实现多任务,但是线程只需要很少的资源就可以实现多任务,
就像是同一个流水线,然后有很多的工人,
流水线就是进程,流水线上的工人就是一个线程,
所以开发的时候最长使用的是线程,这个比较轻,
------------
比如一个网易云音乐,既要下载音乐,又要播放音乐,
这就是一个进程,两个线程,
进程之间是独立的,
线程是依赖于进程的,没有进程就没有线程,
----------------------
所以这是非常的重要的,
############### 网络编程 ##############
以上是关于python语法基础-并发编程-线程-长期维护的主要内容,如果未能解决你的问题,请参考以下文章