进程队列补充、socket实现服务器并发、线程完结
1.队列补充
队列内部是管道+锁(数据在队列中是阻塞的)
2.关于python并发与并行的补充
解释型语言单个进程下多个线程不可以并行,但是向C语言等其他语言中在多核情况下是可以实现并行的,所有语言在单核下都是无法实现并行的,只能并发。
3.TCP服务端实现并发
#服务端
import socket
from threading import Thread
server = socket.socket()
server.bind(('127.0.0.1',6666))
server.listen(5)
def serv(conn,addr):
while True:
try:
print(addr)
rec_data = conn.recv(1024).decode('utf-8')
print(rec_data)
send_data = rec_data.upper()
conn.send(send_data.encode('utf-8'))
except Exception as e:
print(e)
break
while True:
conn,addr = server.accept()
t = Thread(target=serv,args=(conn,addr))
t.start()
#客户端
import socket
import time
client = socket.socket()
client.connect(('127.0.0.1',6666))
while True:
client.send(b'hello')
data = client.recv(1024)
print(data)
time.sleep(1)
4.GIL全局解释器锁
在CPython中,全局解释器锁(即GIL)是一个互斥锁,可以防止一个进程中的多个线程同时(并行)执行。 锁定是必要的,主要是因为CPython的内存管理不是线程安全的。GIL的存在就是为了保证线程安全。
什么是保证线程安全呢?
同一进程的所有线程都运行在一个进程内,毫无疑问这些线程具有以下几个特点:
1、所有数据都是共享的,这其中,代码作为一种数据也是被所有线程共享的(test.py的所有代码以及Cpython解释器的所有代码)
例如:test.py定义一个函数work(代码内容如下图),在进程内所有线程都能访问到work的代码,于是我们可以开启三个线程然后target都指向该代码,能访问到意味着就是可以执行。
2、所有线程的任务,都需要将任务的代码当做参数传给解释器的代码去执行,即所有的线程要想运行自己的任务,首先需要解决的是能够访问到解释器的代码。
综上:
如果多个线程的target=work,那么执行流程是:
多个线程先访问到解释器的代码,即拿到执行权限,然后将target的代码交给解释器的代码去执行
解释器的代码是所有线程共享的,所以垃圾回收线程也可能访问到解释器的代码而去执行,这就导致了一个问题:对于同一个数据100,可能线程1执行x=100的同时,而垃圾回收执行的是回收100的操作,解决这种问题没有什么高明的方法,就是加锁处理,如下图的GIL,保证python解释器同一时间只能执行一个任务的代码。

GIL与Lock
机智的同学可能会问到这个问题:Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为什么这里还需要lock?
首先,我们需要达成共识:锁的目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据
然后,我们可以得出结论:保护不同的数据就应该加不同的锁。
最后,问题就很明朗了,GIL 与Lock是两把锁,保护的数据不一样,前者是解释器级别的(当然保护的就是解释器级别的数据,比如垃圾回收的数据),后者是保护用户自己开发的应用程序的数据,很明显GIL不负责这件事,只能用户自定义加锁处理,即Lock,如下图

注意:多个线程过来执行,一旦遇到IO操作就会立马释放GIL解释器锁,交个下一个先进来的线程。
在纯计算程序中GIL锁起到锁定python解释器的作用,就是一个线程抢到解释器后不会再有其他线程抢到解释器的使用权(直到这个程序遇到IO操作,这时GIL会释放解释器的使用权)。
import time
from threading import Thread,current_thread
number = 100
def task():
global number
number2 = number
number = number2 - 1
print(number,current_thread().name)
for line in range(100):
t = Thread(target=task)
t.start()
99 Thread-1
98 Thread-2
97 Thread-3
96 Thread-4
95 Thread-5
94 Thread-6
93 Thread-7
92 Thread-8
91 Thread-9
90 Thread-10
。
。
。
给这个程序加上IO操作看下打印结果:
import time
from threading import Thread,current_thread
number = 100
def task():
global number
number2 = number
#print(number)
time.sleep(1)
number = number2 - 1
#time.sleep(1)#如果sleep放在这里,则会打印结果都是0,这是因为线程间数据是共享的
print(number,current_thread().name)
for line in range(100):
t = Thread(target=task)
t.start()
99 Thread-24
99 Thread-23
99 Thread-22
99 Thread-20
99 Thread-18
99 Thread-17
99 Thread-15
99 Thread-21
99 Thread-14
.
.
.
5.验证多线程的作用
什么时候使用多线程,什么时候使用多进程,多线程和多进程各有什么优缺点?
结论:在计算密集型程序中使用多进程,这时能够充分发挥计算机多核的优势;在IO密集型的程序中使用多线程,这时能够充分发挥多线程对CPU高校利用率的优势。高效执行多个进程,内有多个IO密集型程序,要使用多进程+多线程。
对结论的验证:
运算密集型操作验证:
from threading import Thread
from multiprocessing import Process
import os,time
#计算密集型多进程下运行
def work1():
number = 0
for line in range(50000000):
number += 1
if __name__ == '__main__':
#测试计算密集型
print(os.cpu_count())#打印CPU有几个内核
start_time = time.time()
list1 = []
for line in range(4):
p = Process(target=work1)
list1.append(p)
p.start()
#p.join()#join如果放在这里比在列表里执行速度慢,是因为在列表里是等所有的进程都起来之后再告诉系统要加join,而在第一个for循环里面则是每起一个进程都会告诉系统一次,这个过程需要时间。
for p in list1:
p.join()
end_time = time.time()
print(f'程序执行的时间{end_time - start_time}')
#12.24461030960083
#多线程下运行
from threading import Thread
from multiprocessing import Process
import os,time
#计算密集型
def work1():
number = 0
for line in range(50000000):
number += 1
#IO密集型
def work2():
time.sleep(1)
if __name__ == '__main__':
#测试计算密集型
print(os.cpu_count())#打印CPU有几个内核
start_time = time.time()
list1 = []
for line in range(4):
p = Thread(target=work1)
list1.append(p)
p.start()
#p.join()#4.407487869262695
for p in list1:
p.join()
end_time = time.time()
print(f'程序执行的时间{end_time - start_time}')
#16.305360794067383
这里本人的测试结果是在计算的数据比较大时开启多进程才会有优势,如果运算数据比较小,开启多线程运算速度反而比开启多进程快得多。
IO密集型操作验证:
#多进程测试
from threading import Thread
from multiprocessing import Process
import os,time
#IO密集型
def work2():
time.sleep(1)
if __name__ == '__main__':
#测试IO密集型
print(os.cpu_count())#打印CPU有几个内核
start_time = time.time()
list1 = []
for line in range(4):
p = Process(target=work2)
list1.append(p)
p.start()
#p.join()#4.407487869262695
for p in list1:
p.join()
end_time = time.time()
print(f'程序执行的时间{end_time - start_time}')
#2.642327070236206
#多线程测试
from threading import Thread
from multiprocessing import Process
import os,time
#IO密集型
def work2():
time.sleep(1)
if __name__ == '__main__':
#测试IO密集型
print(os.cpu_count())#打印CPU有几个内核
start_time = time.time()
list1 = []
for line in range(4):
p = Thread(target=work2)
list1.append(p)
p.start()
#p.join()#4.407487869262695
for p in list1:
p.join()
end_time = time.time()
print(f'程序执行的时间{end_time - start_time}')
#1.0189073085784912
可以看出在IO密集型操作中,开相同数量的程要比开相同数量的线程执行速度慢
6.死锁现象
所谓死锁:是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁。
from threading import Lock,Thread,current_thread
import time
mutex_a = Lock()#实例化一把锁
mutex_b = Lock()
class MyThread(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
mutex_a.acquire()
print(f'用户{self.name}抢到了锁a')
mutex_b.acquire()
print(f'用户{self.name}抢到了锁b')
mutex_b.release()
print(f'用户{self.name}释放了锁b')
mutex_a.release()
print(f'用户{self.name}释放了锁a')
def func2(self):
mutex_b.acquire()
print(f'用户{self.name}抢到了锁b')
time.sleep(1)
mutex_a.acquire()
print(f'用户{self.name}抢到了锁a')
mutex_a.release()
print(f'用户{self.name}释放了锁a')
mutex_b.release()
print(f'用户{self.name}释放锁b')
for line in range(10):
t = MyThread()
t.start()
用户Thread-1抢到了锁a
用户Thread-1抢到了锁b
用户Thread-1释放了锁b
用户Thread-1释放了锁a
用户Thread-1抢到了锁b
用户Thread-2抢到了锁a
当线程1抢到了b而线程2抢到了a时,线程1要执行强a的任务,而线程2要执行抢b的任务,而两把锁都没有释放手中的锁,所以就造成了死锁的现象。
7.递归锁
递归锁用于解决死锁问题,递归锁可以被多个线程使用,当第一个线程使用时,遇到几把锁,它的引用计数就为几,只有当它的引用计数为零时才会给第二个线程使用。这样拥有递归锁的那个线程就可以将自己的锁里的内容执行完然后,其他使用递归锁的线程才可以执行。也就是但是第一个使用这把锁的线程会对这把锁加一个引用计数,只有引用计数为零时才能真正释放该锁。
from threading import Lock,Thread,RLock
import time
mutex_a = mutex_b = RLock()#实例化一把递归锁
class MyThread(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
mutex_a.acquire()
print(f'用户{self.name}抢到了锁a')
mutex_b.acquire()
print(f'用户{self.name}抢到了锁b')
mutex_b.release()
print(f'用户{self.name}释放了锁b')
mutex_a.release()
print(f'用户{self.name}释放了锁a')
def func2(self):
mutex_b.acquire()
print(f'用户{self.name}抢到了锁b')
time.sleep(0.1)
mutex_a.acquire()
print(f'用户{self.name}抢到了锁a')
mutex_a.release()
print(f'用户{self.name}释放了锁a')
mutex_b.release()
print(f'用户{self.name}释放锁b')
for line in range(10):
t = MyThread()
t.start()
8.信号量(了解)
互斥锁比喻成一个家用马桶,同一时间只能一个人用;信号量比喻成一个公厕,同一时间可以有多个人用。
from threading import Semaphore,Lock,current_thread,Thread
import time
sm = Semaphore(5)#5个马桶
#mutex = Lock()#一个马桶
def task():
sm.acquire()
print(f'{current_thread().name}执行任务')
time.sleep(1)
sm.release()
for line in range(20):
t = Thread(target=task)
t.start()
#这段代码的功能是每次让五个线程并发执行
9.线程队列
线程Q:线程队列 FIFO(先进先出)就是队列,面试会问。
queue 是python解释器自带的模块,进程中的Queue是python解释器自带的模块multiprocessing里面的一个类。
普通队列(FIFO):先进先出
特殊队列(LIFO):后进先出
import queue
q = queue.Queue()#先进先出
q.put(1)
q.put(2)
print('q',q.get())
q1 = queue.LifoQueue()#先进后出
q1.put(1)
q1.put(2)
print('q1',q1.get())
q 1
q1 2
优先级队列
优先级根据数字来判断,数字为几优先级就为几,1的优先级最高
若参数中传的是元组,优先级以第一个元素的数字大小为准,如果元组的第一个元素都为字母则以字母的ASCII码为准,如果第一个元素的优先级相同就判断第二个元素的顺序(汉字也会判断顺序)但是如果同一列的元素既有数字又有字母会报错。
import queue
q2 = queue.PriorityQueue()#优先级队列
q2.put((1,2,3))
q2.put((2,2,3))
q2.put((3,2,3))
print(q2.get())
(1, 2, 3)
句柄
句柄(handle),有多种意义,第一种解释:句柄是一种特殊的智能指针。当一个应用程序要引用其他系统(如数据库、操作系统)所管理的内存块或对象时,就要使用句柄。
进程队列补充、socket实现服务器并发、线程完结的更多相关文章
- 利用队列Queue实现一个多并发“线程池”效果的Socket程序
本例通过利用类Queue建立了一个存放着Thread对象的“容器对象”,当Client端申请与Server端通信时,在Server端的“链接循环”中每次拿出一个Thread对象去创建“线程链接”,从而 ...
- 进程队列补充-创建进程队列的另一个类JoinableQueue
JoinableQueue同样通过multiprocessing使用. 创建队列的另外一个类: JoinableQueue([maxsize]):这就像是一个Queue对象,但队列允许项目的使用者通知 ...
- linux socket高性能服务器处理框架
这个博客很多东西 http://blog.csdn.net/luozhonghua2014/article/details/37041765 思考一种高性能的服务器处理框架 1.首先需要一个内存池 ...
- Python进阶----异步同步,阻塞非阻塞,线程池(进程池)的异步+回调机制实行并发, 线程队列(Queue, LifoQueue,PriorityQueue), 事件Event,线程的三个状态(就绪,挂起,运行) ,***协程概念,yield模拟并发(有缺陷),Greenlet模块(手动切换),Gevent(协程并发)
Python进阶----异步同步,阻塞非阻塞,线程池(进程池)的异步+回调机制实行并发, 线程队列(Queue, LifoQueue,PriorityQueue), 事件Event,线程的三个状态(就 ...
- [转帖]iis最大并发连接数、队列长度、最大并发线程数、最大工作进程数
iis最大并发连接数.队列长度.最大并发线程数.最大工作进程数 2018-10-17 12:49:03 牛兜兜 阅读数 2952 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议 ...
- Disruptor——一种可替代有界队列完成并发线程间数据交换的高性能解决方案
本文翻译自LMAX关于Disruptor的论文,同时加上一些自己的理解和标注.Disruptor是一个高效的线程间交换数据的基础组件,它使用栅栏(barrier)+序号(Sequencing)机制协调 ...
- 线程、进程、daemon、GIL锁、线程锁、递归锁、信号量、计时器、事件、队列、多进程
# 本文代码基于Python3 什么是进程? 程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程.程序和进程的区别就在于:程序是指令的集合,它是进程运行 ...
- C#高性能大容量SOCKET并发(六):超时Socket断开(守护线程)和心跳包
原文:C#高性能大容量SOCKET并发(六):超时Socket断开(守护线程)和心跳包 守护线程 在服务端版Socket编程需要处理长时间没有发送数据的Socket,需要在超时多长时间后断开连接,我们 ...
- socket编程之并发回射服务器3
在socket编程之并发回射服务器一文中,服务器采用多进程的方式实现并发,本文采用多线程的方式实现并发. 多线程相关API: // Compile and link with -pthread int ...
随机推荐
- 将Unix时间戳转换为Date、Json属性动态生成反序列化、序列化指定属性
实体类 public class Test { [JsonIgnore] public string GetDate { get { return GetTime.ToString("yyy ...
- 【HANA系列】SAP HANA数据处理的理解与分析一
公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[HANA系列]SAP HANA数据处理的理解与 ...
- hdu5993/2016icpc青岛L
zz:https://www.cnblogs.com/ytytzzz/p/9674661.html 题意:给一棵树,每次询问删掉两条边,问剩下的三棵树的最大直径点10W,询问10W,询问相互独立 So ...
- 2.nginx配置详细说明
Nginx配置详解 nginx概述 nginx是一款自由的.开源的.高性能的HTTP服务器和反向代理服务器:同时也是一个IMAP.POP3.SMTP代理服务器:nginx可以作为一个HTTP服务器进行 ...
- Java基础/Socket.io双向通信
Socket.io基础知识(一) (一).socket.io提供了基于事件的实时双向通讯 Web端与服务端实时数据传输方式: 1.Ajax轮询方式(最早应用) 原理:设置定时器,定时通过Ajax同 ...
- IntelliJ IDEA 快捷键终极大全
自动代码 常用的有fori/sout/psvm+Tab即可生成循环.System.out.main方法等boilerplate样板代码 . 例如要输入for(User user : users)只需输 ...
- [CF585E]Marbles
Description: 给定一个序列 \(a_i\) ,每次可以交换相邻两个元素,求使序列变成若干个极大连续段,每个极大连续段内部的值相同且任意两个极大连续段的值互不相同. \(n\le 4\tim ...
- Linux环境安装mongodb
介绍 上篇介绍了Linux环境下安装Node.js的步骤,紧接着来安装mongodb.另外,推荐我的另一篇 Windows下图文详解Mongodb安装及配置,先在Windows下熟悉下mongodb, ...
- 《剑指offer》面试题25 二叉树中和为某一值的路径 Java版
(判断是否有从根到叶子节点的路径,其和为给定值.记录这些路径.) 我的方法:这道题我是按照回溯的思路去做的,我们需要一个数据结构来保存和删除当前递归函数中添加的值.这里要打印一条路径,我们可以选择Li ...
- ubuntu 新建root用户
1. sudo passwd :设置root用户密码 2. 切换用户 方式一:su 方式二: su root 3. 新增普通用户