文章目录
并发
并发和并行的区别
- 并行:同时做某些事,可以互不干扰的同一个时刻做几件事(处理事情的能力)
- 并发:也是同时做某些事,但是强调,一个时段内有事情要处理(需要处理的事情)
并发的解决
- 队列,缓冲区:先进先出,解决了资源使用的问题。排成的队列,其实就是一个缓冲地带,就是缓冲区
- 争抢:争抢也是一种高并发解决方案,但是,这样可能不好,因为有可能有些程序很长时间抢不到资源,(谁抢到资源就上锁,排他性的锁,这是一种锁机制)
- 预处理:一种提前加载用户需要的数据的思路,预处理思想,缓存常用。
- 并行:多进程、多线程实现并行处理,来解决并发问题。注意这些都是水平扩展思想(增加硬件措施,成本提高)
- 提速:提高单个CPU性能,或单个服务器安装更多的CPU。这是一种垂直扩展思想
- 消息中间件:类似于缓冲,常见的消息中间件有RabbitMQ、ActiveMQ(Apache)、RocketMQ(阿里Apache)、kafka(Apache)等
进程与线程
- 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础
- 进程和程序的关系:程序是源代码编译后的文件,而这些文件存放在磁盘上。当程序被操作系统加载到内存中,就是进程,进程中存放着指令和数据(资源),它也是线程的容器。
- 代操作系统提出进程的概念,每一个进程都认为自己独占所有的计算机硬件资源。
- 线程:是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC)、寄存器集合和堆栈组成
线程的状态
状态 | 含义 |
---|---|
就绪(Ready) | 线程能够运行,但在等待被调度。可能线程刚刚创建启动,或刚刚从阻塞中恢复,或者被其他线程抢占 |
运行(Running) | 线程正在运 |
行阻塞(Blocked) | 线程等待外部事件发生而无法运行,如I/O操作 |
终止(Terminated) | 线程完成,或退出,或被取消 |
python的线程开发
python的线程开发使用标准库 threading
进程靠线程执行代码,至少有一个主线程,其他线程就是工作线程
主线程是第一个启动的线程
Thread 类
# 签名
def __init__(self,group=None,target=None,name=None,args=(),kwargs=None,*,daemon=None):
参数名 | 含义 |
---|---|
target | 线程调用的对象,就是目标函数 |
name | 为线程起个名字 |
args为 | 目标函数传递实参,元组 |
kwargs | 为目标函数关键字传参,字典 |
线程启动
import threading
def worker():
print('working')
print('Fineshed')
t = threading.Thread(target=worker,name='worker') # 创建线程对象
t.start() # 启动线程
- 通过threading.Thread创建一个线程对象,target是目标函数,可以使用name为线程指定名称。但是线程没有启动,需要调用start方法。
- 线程之所以执行函数,是因为线程中就是要执行代码的,而最简单的封装就是函数,所以还是函数调用。函数执行完,线程也就退出了
线程退出
- python没有提供线程退出的方法,线程在项目情况时退出
- 线程函数内语句执行完毕
- 线程函数中抛出未处理的异常
Python的线程没有优先级、没有线程组的概念,也不能被销毁、停止、挂起,那也就没有恢复、中断了。
线程的传参
import threading
import time
def add(x:int,y:int):
print('{}+{}={},{}'.format(x,y,x+y,threading.current_thread().ident))
t1 = threading.Thread(target=add,name='add',args=(1,2)) #创建线程对象
t1.start() # 启动线程
time.sleep(2)
t2 = threading.Thread(target=add,name='add',args=(10,),kwargs={'y':8})
t2.start()
time.sleep(1)
t3 = threading.Thread(target=add,name='add',kwargs={'x':20,'y':8})
t3.start()
-----------------------------------------------------------------
1+2=3,18404
10+8=18,8252
20+8=28,9672
线程传参和函数传参没有区别,本质上就是函数传参
threading的属性和方法
名称 | 含义 |
---|---|
current_thread() | 返回当前线程对象 |
main_thread() | 返回主线程对象 |
active_count() | 当前处于alive状态的线程个数 |
enumerate() | 返回所有活着的线程的列表,不包括已经终止的线程和未开始的线程 |
get_ident() | 返回当前线程的ID,非0整数 |
- active_count、enumerate方法返回的值还包括主线程
import threading
import time
def showthreadinfo():
print('current thread={},main thread={},active thread={},current active thread.ident={}'.format(threading.current_thread(),\
threading.main_thread(),threading.active_count(),threading.current_thread().ident))
time.sleep(1)
t3 = threading.Thread(target=showthreadinfo(),name='showthreadinfo')
t3.start()
-----------------------------------------------------------------------
current thread=<_MainThread(MainThread, started 3524)>,main thread=<_MainThread(MainThread, started 3524)>,active thread=1,current active thread.ident=3524
Thread实例的属性和方法
名称 | 含义 |
---|---|
name | 只是一个名字,只是个标识,名称可以重复,getName(),setName()获取,设置这个名词 |
ident | 线程Id,它是非0 整数,线程启动后才会有ID,否则None,线程退出,此ID依旧可以访问 |
is_alive() | 返回线程是否活着 |
注意:线程的name这是一个名称,可以重复;ID必须唯一,但可以在线程退出后再利用。
名称 | 含义 |
---|---|
start() | 启动线程,每一个线程必须且只能执行该方法一次 |
run() | 运行线程函数 |
start()方法,run()方法和区别
import threading
import time
def worker():
print(threading.enumerater()) # 通过enumerate()查看线程列表
for i in range(2):
time.sleep(1)
print('i am working')
print('working finished')
class myThread(threading.Thread): # myThread 继承了threading 里的Thread类
def start(self):
print('start---')
super().start()
def run(self):
print('run---')
super().run() # 引用了父类里的run函数
t = myThread(target=worker,name='worker')
t.start()
----------------------------------------------------------------------
start---
run---
[<_MainThread(MainThread, started 15936)>, <myThread(worker, started 17300)>]
i am working
i am working
working finished
run()方法
import threading
import time
def worker():
print(threading.enumerater()) # 通过enumerate()查看线程列表
for i in range(2):
time.sleep(1)
print('i am working')
print('working finished')
class myThread(threading.Thread): # myThread 继承了threading 里的Thread类
def start(self):
print('start---')
super().start()
def run(self):
print('run---')
super().run() # 引用了父类里的run函数
t = myThread(target=worker,name='worker')
t.run()
----------------------------------------------------------------------
run---
i am working
i am working
working finished
- start()方法会调用run()方法,而run()方法可以运行函数
- run()方法和start()方法的区别在于:
使用strat方法启动新线程,但是使用run()方法并没有启动新线程,只是在主线程中调用了一个函数而已,因此启动线程需要使用start方法,而且start方法只能调用一次.
多线程
一个进程中如果有多个线程,就是多线程,实现一种并发
import threading
import time
def worker():
print(threading.enumerate()) # 通过enumerate()查看线程列表
for i in range(2):
time.sleep(1)
print('i am working')
print('working finished')
class myThread(threading.Thread): # myThread 继承了threading 里的Thread类
def start(self):
print('start---')
super().start()
def run(self):
print('run---')
super().run() # 引用了父类里的run函数
t1 = myThread(target=worker,name='worker1')
t1.start() # 启动第一个线程
t2 = myThread(target=worker,name='worker2')
t2.start() # 启动第二个线程
-----------------------------------------------------------------------
start---
run---
[<_MainThread(MainThread, started 3720)>, <myThread(worker1, started 16024)>]
start---
run---
[<_MainThread(MainThread, started 3720)>, <myThread(worker1, started 16024)>, <myThread(worker2, started 1396)>]
i am working
i am working
i am working
working finished
i am working
working finished
- 使用start方法启动线程后,进程内有多个活动的线程并行的工作,就是多线程
- 一个进程中至少有一个线程,并作为程序的入口,这个线程就是主线程。一个进程至少有一个主线程。其他线程称为工作线程
- 如下:所有线程的目标只有一个,就是要count递增到100,一旦一个线程递增到100,其他线程就不应该再递增才是我们需要的程序,(换种思维,所有工作线程都是帮忙主线程完成目标,一旦有一个工作线程完成目标,其余线程不再工作,只走过场而已)
import threading
import time
count = []
def worker(n):
while len(count) < n:
time.sleep(0.001)
count.append(1)
print('over',len(count),threading.current_thread().name)
for i in range(5):
print('start{}'.format(1+i))
threading.Thread(target=worker,name='worker{}'.format(1+i),args=(100,)).start()
--------------------------------------------------------------------------------------
start1
start2
start3
start4
start5
over 100 worker3
over 101 worker4
over 102 worker5
over 103 worker1
over 104 worker2
#基本上是同时启动5个线程,但在count还差1是,5个线程同时递增1,就到了104,这样不是我们所需要的代码
解决上面的问题,用锁
import threading
import time
lock = threading.Lock()
count = []
def worker(n):
a = 0
flag = False
while True:
lock.acquire()
if len(count) >= n:
flag = True
if not flag:
time.sleep(0.001)
a += 1
count.append(1)
lock.release()
if flag:
break
print('over', len(count), threading.current_thread().name, a)
for i in range(5):
print('start{}'.format(1+i))
threading.Thread(target=worker,name='worker{}'.format(1+i),args=(100,)).start()
--------------------------------------------------------------------------------------
start1
start2
start3
start4
start5
over 100 worker5 28
over 100 worker2 67
over 100 worker3 0
over 100 worker4 0
over 100 worker1 5
# worker3和worker4线程并没有参与计算,一直被阻塞到目标完成,最后什么事都没有干,走了个过场而已
线程的安全
import threading
def worker():
for x in range(5):
print("{} is running.".format(threading.current_thread().name))
for x in range(1, 8): # 可以增加线程数
name = "worker{}".format(x)
t = threading.Thread(name=name, target=worker)
t.start()
------------------------------------------------------------
worker1 is running.worker2 is running.
worker2 is running.
worker2 is running.
...
- 从输出结果来看,print函数被切换打断了,print函数分两步,第一步打印字符串,第二步打印换行符,就在这之间,发生了线程的切换,这说明print函数的线程不安全的
不让print打印换行,字符串是不可变的类型,它可以作为一个整体不可分割输出。end=’'就不在让print输出换行了
使用logging
标准库里面的logging模块,日志处理模块,线程安全的,生成环境代码都使用logging
import threading
import logging
def worker():
for x in range(5):
logging.warning('{} is runing'.format(threading.current_thread()))
# print("{} is running.".format(threading.current_thread().name),end='')
for x in range(1, 8): # 可以增加线程数
name = "worker{}".format(x)
t = threading.Thread(name=name, target=worker)
t.start()
------------------------------------------------------------
WARNING:root:<Thread(worker1, started 2320)> is runing
WARNING:root:<Thread(worker2, started 14740)> is runing
WARNING:root:<Thread(worker3, started 11444)> is runing
......
daemon线程和non-daemon线程
# 源码中定义的daemon,默认为None
def __init__(self, group=None, target=None, name=None,
args=(), kwargs=None, *, daemon=None):
线程daemon属性,如果设定就是用户的设置,否则就取当前线程daemon的值,主线程是non-daemon线程,即daemon=False
源码如下
def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None):
if daemon is not None:
self._daemonic = daemon
else:
self._daemonic = current_thread().daemon
import threading
import time
def worker():
print(threading.enumerate())
for i in range(2):
time.sleep(1)
print('i am working')
print('working finished')
class myThread(threading.Thread):
def start(self):
print('start---')
super().start()
def run(self):
print('run---')
super().run()
t1 = myThread(target=worker,name='worker1',daemon=True) #daemon值改为True,则主线程不等工作线程结束,主线程就结束了
t1.start()
print('module finished')
-----------------------------------------------
start---
run---
[<_MainThread(MainThread, started 8792)>, <myThread(worker1, started daemon 16572)>]
module finished #结果看出,主线程快于工作线程结束
名称 | 含义 |
---|---|
daemon属性 | 表示线程是否是daemon线程,这个值必须在start()之前设置,否则引发RuntimeError异常 |
isDaemon() | 是否是daemon线程 |
setDaemon | 设置为daemon线程,必须在start方法之前设置 |
import threading
import time
def worker(name, timeout):
time.sleep(timeout)
print('{} working'.format(name))
t = threading.Thread(target=worker, args=('t1', 1), daemon=False)
t.start() # t1 打印执行,因为t1为non-daemon线程,主线程会等t1线程结束后才会结束
t2 = threading.Thread(target=worker, args=('t2', 3), daemon=True)
t2.start() # t2 并未打印执行,因为t2为daemon线程,主线程不会等t2线程
print('main thread exits')
-----------------------------------------------
main thread exits
t1 working
- 总结:
- 线程具有一个daemon属性,可以手动设置为True或False,也可以不设置,则取默认值None。
- 如果不设置daemon,就取当前线程的daemon来设置它。
- 主线程是non-daemon线程,即daemon = False。
- 从主线程创建的所有线程的不设置daemon属性,则默认都是daemon = False,也就是non-daemon线程
- Python程序在没有活着的non-daemon线程运行时,程序退出,也就是除主线程之外剩下的只能都是daemon线程,主线程才能退出,否则主线程就只能等待。
jion方法
- daemon线程使用join方法后daemon线程执行完了,主线程才退出
import threading
import time
def worker():
print(threading.enumerate()) # 通过enumerate()查看线程列表
for i in range(2):
time.sleep(1)
print('i am working')
print('working finished')
class myThread(threading.Thread): # myThread 继承了threading 里的Thread类
def start(self):
print('start---')
super().start()
def run(self):
print('run---')
super().run() # 引用了父类里的run函数
t1 = myThread(target=worker,name='worker1',daemon=True)
t1.start()
t1.join(5)
print('---------------------------------')
print('module finished')
----------------------------------------------------------------------
start---
run---
[<_MainThread(MainThread, started 4248)>, <myThread(worker1, started 508)>]
i am working
i am working
working finished
---------------------------------
module finished #使用join方法后daemon线程执行完了,主线程才退出(t1线程耗时在2秒左右,主线程被阻塞了5秒,工作线程快于主线程结束)
- join(timeout=None),是线程的标准方法之一。
- 一个线程中调用另一个线程的join方法,调用者将被阻塞,直到被调用线程终止。
- 一个线程可以被join多次。
- timeout参数指定调用者等待多久,没有设置超时,就一直等到被调用线程结束。调用谁的join方法,就是join谁,就要等谁
threading.local类
- python提供threading.local类,将这个类实例化得到一个全局对象,但是不同的线程使用这个对象存储的数据其他线程看不见(全局对象和局部变量效果一样)。
#用threading.local类
import threading
import time
# 全局对象
global_data = threading.local()
def worker():
global_data.x = 0
for i in range(100):
time.sleep(0.0001)
global_data.x += 1
print(threading.current_thread(), global_data.x)
for i in range(10):
threading.Thread(target=worker).start()
--------------------------------------------------------
<Thread(Thread-349, started 7928)><Thread(Thread-351, started 17588)> 100
<Thread(Thread-348, started 5784)> 100
<Thread(Thread-352, started 10920)> 100
<Thread(Thread-350, started 8856)> 100
100
# 局部变量
import threading
import time
def worker():
X = 0
for i in range(100):
time.sleep(0.0001)
X += 1
print(threading.current_thread(), X)
for i in range(10):
threading.Thread(target=worker).start()
-------------------------------------------------
<Thread(Thread-345, started 11968)> 100
<Thread(Thread-344, started 1280)> 100
<Thread(Thread-343, started 17272)> 100
<Thread(Thread-346, started 18288)> 100
<Thread(Thread-347, started 17928)> 100
import threading
global_data = threading.local()
global_data.x = 100
def worker():
print(global_data)
print(global_data.x)
threading.Thread(target=worker).start()
-------------------------------------------------------
<_thread._local object at 0x000001B37CB90E08>
Exception in thread Thread-353:
...
AttributeError: '_thread._local' object has no attribute 'x'
以上代码运行结果可以看出
-
另起一个线程打印 global_data.x 出错了。AttributeError: ‘_thread._local’ object has no attribute 'x’但是,global_data打印没有出错,说明看到global_data,但是global_data中的x看不到,这个x不能跨线程。
-
threading.local类构建了一个大字典,存放所有线程相关的字典,定义如下:{ id(Thread) -> (ref(Thread), thread-local dict) }每一线程实例的id为key,元组为value。value中2部分为,线程对象引用,每个线程自己的字典。
-
本质:
运行时,threading.local实例处在不同的线程中,就从大字典中找到当前线程相关键值对中的字典,
覆盖threading.local实例的__dict__。这样就可以在不同的线程中,安全地使用线程独有的数据,
做到了线程间数据隔离,如同本地变量一样安全。