目录

一、开启线程的两种方式1.1 直接利用利用threading.Thread()类实例化1.2 创建一个类,并继承Thread类1.3 在一个进程下开启多个线程与在一个进程下开启多个子进程的区别1.3.1 谁的开启速度更快?1.3.2 看看PID的不同1.3.3 练习1.3.4 线程的join与setDaemon1.3.5 线程相关的其他方法补充二、 Python GIL2.1 什么是全局解释器锁GIL2.2 全局解释器锁GIL设计理念与限制三、 Python多进程与多线程对比
四、锁4.1 同步锁GIL vs Lock4.2 死锁与递归锁4.3 信号量Semaphore4.4 事件Event4.5 定时器timer4.6 线程队列queue五、协程5.1 yield实现协程5.2 greenlet实现协程5.3 gevent实现协程六、socketserver实现并发6.1 ThreadingTCPServer七、基于UDP的套接字

一、开启线程的两种方式

在python中开启线程要导入threading,它与开启进程所需要导入的模块multiprocessing在使用上,有很大的相似性。在接下来的使用中,就可以发现。

同开启进程的两种方式相同:

1.1 直接利用利用threading.Thread()类实例化

from threading import Thread
import time
def sayhi(name):time.sleep(2)print('%s say hello' %name)if __name__ == '__main__':t=Thread(target=sayhi,args=('egon',))t.start()print('主线程')

1.2 创建一个类,并继承Thread

from threading import Thread
import time
calss Sayhi(Thread):def __init__(self,name):super().__init__()self.name = namedef run(self):time.sleep(2)print("%s say hello" %self.name)if __name__ == "__main__":t = Sayhi("egon")t.start()print("主线程")

1.3 在一个进程下开启多个线程与在一个进程下开启多个子进程的区别

1.3.1 谁的开启速度更快?

from threading import Thread
from multiprocessing import Process
import osdef work():print('hello')if __name__ == '__main__':#在主进程下开启线程t=Thread(target=work)t.start()print('主线程/主进程')'''打印结果:hello主线程/主进程'''#在主进程下开启子进程t=Process(target=work)t.start()print('主线程/主进程')'''打印结果:主线程/主进程hello'''

结论:由于创建子进程是将主进程完全拷贝一份,而线程不需要,所以线程的创建速度更快。

1.3.2 看看PID的不同

from threading import Thread
from multiprocessing import Process
import osdef work():print('hello',os.getpid())if __name__ == '__main__':#part1:在主进程下开启多个线程,每个线程都跟主进程的pid一样t1=Thread(target=work)t2=Thread(target=work)t1.start()t2.start()print('主线程/主进程pid',os.getpid())#part2:开多个进程,每个进程都有不同的pidp1=Process(target=work)p2=Process(target=work)p1.start()p2.start()print('主线程/主进程pid',os.getpid())'''
hello 13552
hello 13552
主线程pid: 13552
主线程pid: 13552
hello 1608
hello 6324
'''

总结:可以看出,主进程下开启多个线程,每个线程的PID都跟主进程的PID一样;而开多个进程,每个进程都有不同的PID。

1.3.3 练习

练习一:利用多线程,实现socket 并发连接
服务端:

from threading import Thread
from socket import *
import ostcpsock = socket(AF_INET,SOCK_STREAM)
tcpsock.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
tcpsock.bind(("127.0.0.1",60000))
tcpsock.listen(5)def work(conn,addr):while True:try:data = conn.recv(1024)print(os.getpid(),addr,data.decode("utf-8"))conn.send(data.upper())except Exception:breakif __name__ == '__main__':while True:conn,addr = tcpsock.accept()t = Thread(target=work,args=(conn,addr))t.start()"""
开启了4个客户端
服务器端输出:
13800 ('127.0.0.1', 63164) asdf
13800 ('127.0.0.1', 63149) asdf
13800 ('127.0.0.1', 63154) adsf
13800 ('127.0.0.1', 63159) asdf可以看出每个线程的PID都是一样的。
""

客户端:

from socket import *tcpsock = socket(AF_INET,SOCK_STREAM)
tcpsock.connect(("127.0.0.1",60000))while True:msg = input(">>: ").strip()if not msg:continuetcpsock.send(msg.encode("utf-8"))data = tcpsock.recv(1024)print(data.decode("utf-8"))

练习二:有三个任务,一个接收用户输入,一个将用户输入的内容格式化成大写,一个将格式化后的结果存入文件。

from threading import Threadrecv_l = []
format_l = []def Recv():while True:inp = input(">>: ").strip()if not inp:continuerecv_l.append(inp)def Format():while True:if recv_l:res = recv_l.pop()format_l.append(res.upper())def Save(filename):while True:if format_l:with open(filename,"a",encoding="utf-8") as f:res = format_l.pop()f.write("%s\n" %res)if __name__ == '__main__':t1 = Thread(target=Recv)t2 = Thread(target=Format)t3 = Thread(target=Save,args=("db.txt",))t1.start()t2.start()t3.start()

1.3.4 线程的joinsetDaemon

与进程的方法都是类似的,其实multiprocessing模块是模仿threading模块的接口;

from threading import Thread
import time
def sayhi(name):time.sleep(2)print('%s say hello' %name)if __name__ == '__main__':t=Thread(target=sayhi,args=('egon',))t.setDaemon(True) #设置为守护线程,主线程结束,子线程也跟着线束。t.start()t.join()  #主线程等待子线程运行结束print('主线程')print(t.is_alive())

1.3.5 线程相关的其他方法补充

Thread实例对象的方法:

  • isAlive():返回纯种是否是活跃的;
  • getName():返回线程名;
  • setName():设置线程名。

threading模块提供的一些方法:

  • threading.currentThread():返回当前的线程变量
  • threading.enumerate():返回一个包含正在运行的线程的列表。正在运行指线程启动后、结束前,不包括启动前和终止后。
  • threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate())有相同结果。
from threading import Thread
import threading
import osdef work():import timetime.sleep(3)print(threading.current_thread().getName())if __name__ == '__main__':#在主进程下开启线程t=Thread(target=work)t.start()print(threading.current_thread().getName()) #获取当前线程名print(threading.current_thread()) #主线程print(threading.enumerate()) #连同主线程在内有两个运行的线程,返回的是活跃的线程列表print(threading.active_count())  #活跃的线程个数print('主线程/主进程')'''打印结果:MainThread<_MainThread(MainThread, started 140735268892672)>[<_MainThread(MainThread, started 140735268892672)>, <Thread(Thread-1, started 123145307557888)>]2主线程/主进程Thread-1'''

二、 Python GIL

GIL全称Global Interpreter Lock,即全局解释器锁。首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL

2.1 什么是全局解释器锁GIL

Python代码的执行由Python 虚拟机(也叫解释器主循环,CPython版本)来控制,Python 在设计之初就考虑到要在解释器的主循环中,同时只有一个线程在执行,即在任意时刻,只有一个线程在解释器中运行。对Python 虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。
在多线程环境中,Python 虚拟机按以下方式执行:

  1. 设置GIL
  2. 切换到一个线程去运行
  3. 运行: a. 指定数量的字节码指令,或者 b. 线程主动让出控制(可以调用time.sleep(0))
  4. 把线程设置为睡眠状态
  5. 解锁GIL
  6. 再次重复以上所有步骤

在调用外部代码(如C/C++扩展函数)的时候,GIL 将会被锁定,直到这个函数结束为止(由于在这期间没有Python 的字节码被运行,所以不会做线程切换)。

2.2 全局解释器锁GIL设计理念与限制

GIL的设计简化了CPython的实现,使得对象模型,包括关键的内建类型如字典,都是隐含可以并发访问的。锁住全局解释器使得比较容易的实现对多线程的支持,但也损失了多处理器主机的并行计算能力。
但是,不论标准的,还是第三方的扩展模块,都被设计成在进行密集计算任务是,释放GIL。
还有,就是在做I/O操作时,GIL总是会被释放。对所有面向I/O 的(会调用内建的操作系统C 代码的)程序来说,GIL 会在这个I/O 调用之前被释放,以允许其它的线程在这个线程等待I/O 的时候运行。如果是纯计算的程序,没有 I/O 操作,解释器会每隔 100 次操作就释放这把锁,让别的线程有机会执行(这个次数可以通过 sys.setcheckinterval 来调整)如果某线程并未使用很多I/O 操作,它会在自己的时间片内一直占用处理器(和GIL)。也就是说,I/O 密集型的Python 程序比计算密集型的程序更能充分利用多线程环境的好处。

下面是Python 2.7.9手册中对GIL的简单介绍:
The mechanism used by the CPython interpreter to assure that only one thread executes Python bytecode at a time. This simplifies the CPython implementation by making the object model (including critical built-in types such as dict) implicitly safe against concurrent access. Locking the entire interpreter makes it easier for the interpreter to be multi-threaded, at the expense of much of the parallelism afforded by multi-processor machines.
However, some extension modules, either standard or third-party, are designed so as to release the GIL when doing computationally-intensive tasks such as compression or hashing. Also, the GIL is always released when doing I/O.
Past efforts to create a “free-threaded” interpreter (one which locks shared data at a much finer granularity) have not been successful because performance suffered in the common single-processor case. It is believed that overcoming this performance issue would make the implementation much more complicated and therefore costlier to maintain.

从上文中可以看到,针对GIL的问题做的很多改进,如使用更细粒度的锁机制,在单处理器环境下反而导致了性能的下降。普遍认为,克服这个性能问题会导致CPython实现更加复杂,因此维护成本更加高昂。

三、 Python多进程与多线程对比

有了GIL的存在,同一时刻同一进程中只有一个线程被执行?这里也许人有一个疑问:多进程可以利用多核,但是开销大,而Python多线程开销小,但却无法利用多核的优势?要解决这个问题,我们需要在以下几点上达成共识:

  • CPU是用来计算的!
  • 多核CPU,意味着可以有多个核并行完成计算,所以多核提升的是计算性能;
  • 每个CPU一旦遇到I/O阻塞,仍然需要等待,所以多核对I/O操作没什么用处。

当然,对于一个程序来说,不会是纯计算或者纯I/O,我们只能相对的去看一个程序到底是计算密集型,还是I/O密集型。从而进一步分析Python的多线程有无用武之地。

分析:

我们有四个任务需要处理,处理访求肯定是要有并发的效果,解决方案可以是:

  • 方案一:开启四个进程;
  • 方案二:一个进程下,开启四个进程。

单核情况下,分析结果:

  • 如果四个任务是计算密集型,没有多核来并行计算,方案一徒增了创建进程的开销,方案二胜;
  • 如果四个任务是I/O密集型,方案一创建进程的开销大,且进程的切换速度远不如线程,方案二胜。

多核情况下,分析结果:

  • 如果四个任务是密集型,多核意味着并行 计算,在python中一个进程中同一时刻只有一个线程执行用不上多核,方案一胜;
  • 如果四个任务是I/O密集型,再多的核 也解决不了I/O问题,方案二胜。

结论:现在的计算机基本上都是多核,python对于计算密集型的任务开多线程的效率并不能带来多大性能上的提升,甚至 不如串行(没有大量切换),但是,对于I/O密集型的任务效率还是有显著提升的。

代码实现对比

计算密集型:

#计算密集型
from threading import Thread
from multiprocessing import Process
import os
import time
def work():res=0for i in range(1000000):res+=iif __name__ == '__main__':t_l=[]start_time=time.time()for i in range(100):# t=Thread(target=work) #我的机器4核cpu,多线程大概15秒t=Process(target=work) #我的机器4核cpu,多进程大概10秒t_l.append(t)t.start()for i in t_l:i.join()stop_time=time.time()print('run time is %s' %(stop_time-start_time))print('主线程')

I/O密集型:

#I/O密集型
from threading import Thread
from multiprocessing import Process
import time
import os
def work():time.sleep(2) #模拟I/O操作,可以打开一个文件来测试I/O,与sleep是一个效果print(os.getpid())if __name__ == '__main__':t_l=[]start_time=time.time()for i in range(500):# t=Thread(target=work) #run time is 2.195t=Process(target=work) #耗时大概为37秒,创建进程的开销远高于线程,而且对于I/O密集型,多cpu根本不管用t_l.append(t)t.start()for t in t_l:t.join()stop_time=time.time()print('run time is %s' %(stop_time-start_time))

总结:
应用场景:
多线程用于I/O密集型,如socket、爬虫、web
多进程用于计算密集型,如金融分析

四、锁

4.1 同步锁

需求:对一个全局变量,开启100个线程,每个线程都对该全局变量做减1操作;

不加锁,代码如下:

import time
import threadingnum = 100  #设定一个共享变量
def addNum():global num #在每个线程中都获取这个全局变量#num-=1temp=numtime.sleep(0.1)num =temp-1  # 对此公共变量进行-1操作thread_list = []for i in range(100):t = threading.Thread(target=addNum)t.start()thread_list.append(t)for t in thread_list: #等待所有线程执行完毕t.join()print('Result: ', num)

分析:以上程序开启100线程并不能把全局变量num减为0,第一个线程执行addNum遇到I/O阻塞后迅速切换到下一个线程执行addNum,由于CPU执行切换的速度非常快,在0.1秒内就切换完成了,这就造成了第一个线程在拿到num变量后,在time.sleep(0.1)时,其他的线程也都拿到了num变量,所有线程拿到的num值都是100,所以最后减1操作后,就是99。加锁实现。

加锁,代码如下:

import time
import threadingnum = 100   #设定一个共享变量
def addNum():with lock:global numtemp = numtime.sleep(0.1)num = temp-1    #对此公共变量进行-1操作thread_list = []if __name__ == '__main__':lock = threading.Lock()   #由于同一个进程内的线程共享此进程的资源,所以不需要给每个线程传这把锁就可以直接用。for i in range(100):t = threading.Thread(target=addNum)t.start()thread_list.append(t)for t in thread_list:  #等待所有线程执行完毕t.join()print("result: ",num)

加锁后,第一个线程拿到锁后开始操作,第二个线程必须等待第一个线程操作完成后将锁释放后,再与其它线程竞争锁,拿到锁的线程才有权操作。这样就保障了数据的安全,但是拖慢了执行速度。
注意:with locklock.acquire()(加锁)与lock.release()(释放锁)的简写。

import threadingR=threading.Lock()R.acquire()
'''
对公共数据的操作
'''
R.release()

GIL vs Lock

机智的同学可能会问到这个问题,就是既然你之前说过了,Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为什么这里还需要lock? 

 首先我们需要达成共识:锁的目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据

然后,我们可以得出结论:保护不同的数据就应该加不同的锁。

 最后,问题就很明朗了,GIL 与Lock是两把锁,保护的数据不一样,前者是解释器级别的(当然保护的就是解释器级别的数据,比如垃圾回收的数据),后者是保护用户自己开发的应用程序的数据,很明显GIL不负责这件事,只能用户自定义加锁处理,即Lock

详细的:

因为Python解释器帮你自动定期进行内存回收,你可以理解为python解释器里有一个独立的线程,每过一段时间它起wake up做一次全局轮询看看哪些内存数据是可以被清空的,此时你自己的程序 里的线程和 py解释器自己的线程是并发运行的,假设你的线程删除了一个变量,py解释器的垃圾回收线程在清空这个变量的过程中的clearing时刻,可能一个其它线程正好又重新给这个还没来及得清空的内存空间赋值了,结果就有可能新赋值的数据被删除了,为了解决类似的问题,python解释器简单粗暴的加了锁,即当一个线程运行时,其它人都不能动,这样就解决了上述的问题, 这可以说是Python早期版本的遗留问题。 

4.2 死锁与递归锁

所谓死锁:是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态,或系统产生了死锁。这此永远在互相等待的进程称死锁进程

如下代码,就会产生死锁:

from threading import Thread,Lock
import time
mutexA=Lock()
mutexB=Lock()class MyThread(Thread):def run(self):self.func1()self.func2()def func1(self):mutexA.acquire()print('\033[41m%s 拿到A锁\033[0m' %self.name)mutexB.acquire()print('\033[42m%s 拿到B锁\033[0m' %self.name)mutexB.release()mutexA.release()def func2(self):mutexB.acquire()print('\033[43m%s 拿到B锁\033[0m' %self.name)time.sleep(2)mutexA.acquire()print('\033[44m%s 拿到A锁\033[0m' %self.name)mutexA.release()mutexB.release()if __name__ == '__main__':for i in range(10):t=MyThread()t.start()'''
Thread-1 拿到A锁
Thread-1 拿到B锁
Thread-1 拿到B锁
Thread-2 拿到A锁
然后就卡住,死锁了
'''

解决死锁的方法

避免产生死锁的方法就是用递归锁,在python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock

这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire(获得锁)的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release(释放)后,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,就不会发生死锁的现象了。

mutexA=mutexB=threading.RLock() #一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止。

4.3 信号量Semaphore

同进程的信号量一样。
用一个粗俗的例子来说,锁相当于独立卫生间,只有一个坑,同一时刻只能有一个人获取锁,进去使用;而信号量相当于公共卫生间,例如有5个坑,同一时刻可以有5个人获取锁,并使用。

Semaphore管理一个内置的计数器,每当调用acquire()时,内置计数器-1;调用release()时,内置计数器+1;计数器不能小于0,当计数器为0时,acquire()将阻塞线程,直到其他线程调用release()

实例:
同时只有5个线程可以获得Semaphore,即可以限制最大连接数为5:

import threading
import timesem = threading.Semaphore(5)
def func():if sem.acquire():   #也可以用with进行上下文管理print(threading.current_thread().getName()+"get semaphore")time.sleep(2)sem.release()for i in range(20):t1 = threading.Thread(target=func)t1.start()

利用with进行上下文管理:

import threading
import timesem = threading.Semaphore(5)def func():with sem:   print(threading.current_thread().getName()+"get semaphore")time.sleep(2)for i in range(20):t1 = threading.Thread(target=func)t1.start()

注:信号量与进程池是完全不同一的概念,进程池Pool(4)最大只能产生4个进程,而且从头到尾都只是这4个进程,不会产生新的,而信号量是产生一堆线程/进程。

4.4 事件Event

同进程的一样

线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其他线程通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手,为了解决这些问题我们使用threading库中的Event对象。

Event对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象,而这个Event对象的标志为假,那么这个线程将会被 一直阻塞直至该 标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被 设置 为真的Event对象,那么它将忽略这个事件,继续执行。

Event对象具有一些方法:
event = threading.Event() #产生一个事件对象

  • event.isSet():返回event状态值;
  • event.wait():如果event.isSet() == False,将阻塞线程;
  • event.set():设置event的状态值为True,所有阻塞池的线程进入就绪状态,等待操作系统高度;
  • event.clear():恢复event的状态值False。

应用场景:

例如,我们有多个线程需要连接数据库,我们想要在启动时确保Mysql服务正常,才让那些工作线程去连接Mysql服务器,那么我们就可以采用threading.Event()机制来协调各个工作线程的连接操作,主线程中会去尝试连接Mysql服务,如果正常的话,触发事件,各工作线程会尝试连接Mysql服务。

from threading import Thread,Event
import threading
import time,random
def conn_mysql():print('\033[42m%s 等待连接mysql。。。\033[0m' %threading.current_thread().getName())event.wait()  #默认event状态为False,等待print('\033[42mMysql初始化成功,%s开始连接。。。\033[0m' %threading.current_thread().getName())def check_mysql():print('\033[41m正在检查mysql。。。\033[0m')time.sleep(random.randint(1,3))event.set()   #设置event状态为Truetime.sleep(random.randint(1,3))if __name__ == '__main__':event=Event()t1=Thread(target=conn_mysql) #等待连接mysqlt2=Thread(target=conn_mysql) #等待连接myqslt3=Thread(target=check_mysql) #检查mysqlt1.start()t2.start()t3.start()'''
输出如下:
Thread-1 等待连接mysql。。。
Thread-2 等待连接mysql。。。
正在检查mysql。。。
Mysql初始化成功,Thread-1开始连接。。。
Mysql初始化成功,Thread-2开始连接。。。
'''

注:threading.Eventwait方法还可以接受一个超时参数,默认情况下,如果事件一直没有发生,wait方法会一直阻塞下去,而加入这个超时参数之后,如果阻塞时间超过这个参数设定的值之后,wait方法会返回。对应于上面的应用场景,如果mysql服务器一直没有启动,我们希望子线程能够打印一些日志来不断提醒我们当前没有一个可以连接的mysql服务,我们就可以设置这个超时参数来达成这样的目的:

上例代码修改后如下:

from threading import Thread,Event
import threading
import time,random
def conn_mysql():count = 1while not event.is_set():print("\033[42m%s 第 <%s> 次尝试连接。。。"%(threading.current_thread().getName(),count))event.wait(0.2)count+=1print("\033[45mMysql初始化成功,%s 开始连接。。。\033[0m"%(threading.current_thread().getName()))def check_mysql():print('\033[41m正在检查mysql。。。\033[0m')time.sleep(random.randint(1,3))event.set()time.sleep(random.randint(1,3))if __name__ == '__main__':event=Event()t1=Thread(target=conn_mysql) #等待连接mysqlt2=Thread(target=conn_mysql) #等待连接mysqlt3=Thread(target=check_mysql) #检查mysqlt1.start()t2.start()t3.start()

这样,我们就可以在等待Mysql服务启动的同时,看到工作线程里正在等待的情况。应用:连接池。

4.5 定时器timer

定时器,指定n秒后执行某操作。

from threading import Timerdef hello():print("hello, world")t = Timer(1, hello)  #1秒后执行任务hello
t.start()   # after 1 seconds, "hello, world" will be printed

4.6 线程队列queue

queue队列:使用import queue,用法与进程Queue一样。

queue下有三种队列:

  • queue.Queue(maxsize) 先进先出,先放进队列的数据,先被取出来;
  • queue.LifoQueue(maxsize) 后进先出,(Lifo 意为last in first out),后放进队列的数据,先被取出来
  • queue.PriorityQueue(maxsize) 优先级队列,优先级越高优先取出来。

举例:
先进先出:

import queueq=queue.Queue()
q.put('first')
q.put('second')
q.put('third')print(q.get())
print(q.get())
print(q.get())
'''
结果(先进先出):
first
second
third
'''

后进先出:

import queueq=queue.LifoQueue()
q.put('first')
q.put('second')
q.put('third')print(q.get())
print(q.get())
print(q.get())
'''
结果(后进先出):
third
second
first
'''

优先级队列:

import queueq=queue.PriorityQueue()
#put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
q.put((20,'a'))
q.put((10,'b'))
q.put((30,'c'))print(q.get())
print(q.get())
print(q.get())
'''
结果(数字越小优先级越高,优先级高的优先出队):
(10, 'b')
(20, 'a')
(30, 'c')
'''

五、协程

协程:是单线程下的并发,又称微线程、纤程,英文名:Coroutine协程是一种用户态的轻量级线程,协程是由用户程序自己控制调度的。

需要强调的是:

  1. python的线程属于内核级别的,即由操作系统控制调度(如单线程一旦遇到io就被迫交出cpu执行权限,切换其他线程运行)

  2. 单线程内开启协程,一旦遇到io,从应用程序级别(而非操作系统)控制切换

对比操作系统控制线程的切换,用户在单线程内控制协程的切换,优点如下:

  1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级

  2. 单线程内就可以实现并发的效果,最大限度地利用cpu。

要实现协程,关键在于用户程序自己控制程序切换,切换之前必须由用户程序自己保存协程上一次调用时的状态,如此,每次重新调用时,能够从上次的位置继续执行

(详细的:协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈)

5.1 yield实现协程

我们之前已经学习过一种在单线程下可以保存程序运行状态的方法,即yield,我们来简单复习一下:

  • yiled可以保存状态,yield的状态保存与操作系统的保存线程状态很像,但是yield是代码级别控制的,更轻量级
  • send可以把一个函数的结果传给另外一个函数,以此实现单线程内程序之间的切换 。
#不用yield:每次函数调用,都需要重复开辟内存空间,即重复创建名称空间,因而开销很大
import time
def consumer(item):# print('拿到包子%s' %item)x=11111111111x1=12111111111x3=13111111111x4=14111111111y=22222222222z=33333333333pass
def producer(target,seq):for item in seq:target(item) #每次调用函数,会临时产生名称空间,调用结束则释放,循环100000000次,则重复这么多次的创建和释放,开销非常大start_time=time.time()
producer(consumer,range(100000000))
stop_time=time.time()
print('run time is:%s' %(stop_time-start_time)) #30.132838010787964#使用yield:无需重复开辟内存空间,即重复创建名称空间,因而开销小
import time
def init(func):def wrapper(*args,**kwargs):g=func(*args,**kwargs)next(g)return greturn wrapperinit
def consumer():x=11111111111x1=12111111111x3=13111111111x4=14111111111y=22222222222z=33333333333while True:item=yield# print('拿到包子%s' %item)pass
def producer(target,seq):for item in seq:target.send(item) #无需重新创建名称空间,从上一次暂停的位置继续,相比上例,开销小start_time=time.time()
producer(consumer(),range(100000000))
stop_time=time.time()
print('run time is:%s' %(stop_time-start_time)) #21.882073879241943

缺点:
协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程。
协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程。

协程的定义(满足1,2,3就可以称为协程):

  1. 必须在只有一个单线程里实现并发
  2. 修改共享数据不需加锁
  3. 用户程序里自己保存多个控制流的上下文栈
  4. 附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))

注意:yield切换在没有io的情况下或者没有重复开辟内存空间的操作,对效率没有什么提升,甚至更慢,为此,可以用greenlet来为大家演示这种切换。

5.2 greenlet实现协程

greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator。

安装greenlet模块
pip install greenlet

from greenlet import greenlet
import timedef t1():print("test1,first")gr2.switch()time.sleep(5)print("test1,second")gr2.switch()def t2():print("test2,first")gr1.switch()print("test2,second")gr1 = greenlet(t1)
gr2 = greenlet(t2)
gr1.switch()'''
输出结果:
test1,first
test2,first   #等待5秒
test1,second
test2,second
'''

可以在第一次switch时传入参数

from greenlet import greenlet
import time
def eat(name):print("%s eat food 1"%name)gr2.switch(name="alex")time.sleep(5)print("%s eat food 2"%name)gr2.switch()def play_phone(name):print("%s play phone 1"%name)gr1.switch()print("%s play phone 1" % name)gr1 = greenlet(eat)
gr2 = greenlet(play_phone)
gr1.switch(name="egon")  #可以在第一次switch时传入参数,以后都不需要

注意:greenlet只是提供了一种比generator更加便捷的切换方式,仍然没有解决遇到I/O自动切换的问题,而单纯的切换,反而会降低程序的执行速度。这就需要用到gevent模块了。

5.3 gevent实现协程

gevent是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要是Greenlet,它是以C扩展模块形式接入Python的轻量级协程。greenlet全部运行在主程操作系统进程的内部,但它们被协作式地调试。遇到I/O阻塞时会自动切换任务。

注意:gevent有自己的I/O阻塞,如:gevent.sleep()和gevent.socket();但是gevent不能直接识别除自身之外的I/O阻塞,如:time.sleep(2),socket等,要想识别这些I/O阻塞,必须打一个补丁:from gevent import monkey;monkey.patch_all()

  • 需要先安装gevent模块
    pip install gevent

  • 创建一个协程对象g1
    g1 =gevent.spawn()
    spawn括号内第一个参数是函数名,如eat,后面可以有多个参数,可以是位置实参或关键字实参,都是传给第一个参数(函数)eat的。

from gevent import monkey;monkey.patch_all()
import geventdef eat():print("点菜。。。")gevent.sleep(3)   #等待上菜print("吃菜。。。")def play():print("玩手机。。。")gevent.sleep(5)  #网卡了print("看NBA...")# gevent.spawn(eat)
# gevent.spawn(play)
# print('主') # 直接结束#因而也需要join方法,进程或现场的jion方法只能join一个,而gevent的joinall方法可以join多个
g1=gevent.spawn(eat)
g2=gevent.spawn(play)
gevent.joinall([g1,g2])  #传一个gevent对象列表。
print("主线程")"""
输出结果:
点菜。。。
玩手机。。。    
##等待大概3秒       此行没打印
吃菜。。。
##等待大概2秒          此行没打印
看NBA...
主线程
"""

注:上例中的gevent.sleep(3)是模拟的I/O阻塞。跟time.sleep(3)功能一样。

同步/异步

import gevent
def task(pid):"""Some non-deterministic task"""gevent.sleep(0.5)print('Task %s done' % pid)def synchronous():  #同步执行for i in range(1, 10):task(i)def asynchronous(): #异步执行threads = [gevent.spawn(task, i) for i in range(10)]gevent.joinall(threads)print('Synchronous:')
synchronous()   #执行后,会顺序打印结果print('Asynchronous:')
asynchronous()  #执行后,会异步同时打印结果,无序的。

爬虫应用

#协程的爬虫应用from gevent import monkey;monkey.patch_all()
import gevent
import time
import requestsdef get_page(url):print("GET: %s"%url)res = requests.get(url)if res.status_code == 200:print("%d bytes received from %s"%(len(res.text),url))start_time = time.time()
g1 = gevent.spawn(get_page,"https://www.python.org")
g2 = gevent.spawn(get_page,"https://www.yahoo.com")
g3 = gevent.spawn(get_page,"https://www.github.com")
gevent.joinall([g1,g2,g3])
stop_time = time.time()
print("run time is %s"%(stop_time-start_time))

上以代码输出结果:

GET: https://www.python.org
GET: https://www.yahoo.com
GET: https://www.github.com
47714 bytes received from https://www.python.org
472773 bytes received from https://www.yahoo.com
98677 bytes received from https://www.github.com
run time is 2.501142978668213

应用:
通过gevent实现单线程下的socket并发,注意:from gevent import monkey;monkey.patch_all()一定要放到导入socket模块之前,否则gevent无法识别socket的阻塞。

服务端代码:

from gevent import monkey;monkey.patch_all()
import gevent
from socket import *class server:def __init__(self,ip,port):self.ip = ipself.port = portdef conn_cycle(self):   #连接循环tcpsock = socket(AF_INET,SOCK_STREAM)tcpsock.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)tcpsock.bind((self.ip,self.port))tcpsock.listen(5)while True:conn,addr = tcpsock.accept()gevent.spawn(self.comm_cycle,conn,addr)def comm_cycle(self,conn,addr):   #通信循环try:while True:data = conn.recv(1024)if not data:breakprint(addr)print(data.decode("utf-8"))conn.send(data.upper())except Exception as e:print(e)finally:conn.close()s1 = server("127.0.0.1",60000)
print(s1)
s1.conn_cycle()

客户端代码 :

from socket import *tcpsock = socket(AF_INET,SOCK_STREAM)
tcpsock.connect(("127.0.0.1",60000))while True:msg = input(">>: ").strip()if not msg:continuetcpsock.send(msg.encode("utf-8"))data = tcpsock.recv(1024)print(data.decode("utf-8"))

通过gevent实现并发多个socket客户端去连接服务端

from gevent import monkey;monkey.patch_all()
import gevent
from socket import *def client(server_ip,port):try:c = socket(AF_INET,SOCK_STREAM)c.connect((server_ip,port))count = 0while True:c.send(("say hello %s"%count).encode("utf-8"))msg = c.recv(1024)print(msg.decode("utf-8"))count+=1except Exception as e:print(e)finally:c.close()# g_l = []
# for i in range(500):
#     g = gevent.spawn(client,'127.0.0.1',60000)
#     g_l.append(g)
# gevent.joinall(g_l)#上面注释代码可简写为下面代码这样。threads = [gevent.spawn(client,"127.0.0.1",60000) for i in range(500)]
gevent.joinall(threads)

六、socketserver实现并发

基于TCP的套接字,关键就是两个循环,一个连接循环,一个通信循环。

SocketServer内部使用 IO多路复用 以及 “多线程” 和 “多进程” ,从而实现并发处理多个客户端请求的Socket服务端。即:每个客户端请求连接到服务器时,Socket服务端都会在服务器是创建一个“线程”或者“进程” 专门负责处理当前客户端的所有请求。

socketserver模块中的类分为两大类:server类(解决链接问题)和request类(解决通信问题)

server类:

server类

request类:

request类

线程server类的继承关系:

线程server类的继承关系

进程server类的继承关系:

进程server类的继承关系

request类的继承关系:

request类的继承关系

以下述代码为例,分析socketserver源码:

ftpserver=socketserver.ThreadingTCPServer(('127.0.0.1',8080),FtpServer)
ftpserver.serve_forever()

查找属性的顺序:ThreadingTCPServer --> ThreadingMixIn --> TCPServer->BaseServer

  1. 实例化得到ftpserver,先找类ThreadingTCPServer__init__,在TCPServer中找到,进而执行server_bind,server_active
  2. ftpserver下的serve_forever,在BaseServer中找到,进而执行self._handle_request_noblock(),该方法同样是在BaseServer
  3. 执行self._handle_request_noblock()进而执行request, client_address = self.get_request()(就是TCPServer中的self.socket.accept()),然后执行self.process_request(request, client_address)
  4. ThreadingMixIn中找到process_request,开启多线程应对并发,进而执行process_request_thread,执行self.finish_request(request, client_address)
  5. 上述四部分完成了链接循环,本部分开始进入处理通讯部分,在BaseServer中找到finish_request,触发我们自己定义的类的实例化,去找__init__方法,而我们自己定义的类没有该方法,则去它的父类也就是BaseRequestHandler中找....

源码分析总结:
基于tcp的socketserver我们自己定义的类中的

  • self.server 即套接字对象
  • self.request 即一个链接
  • self.client_address 即客户端地址

基于udp的socketserver我们自己定义的类中的

  • self.request是一个元组(第一个元素是客户端发来的数据,第二部分是服务端的udp套接字对象),如(b'adsf', <socket.socket fd=200, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=('127.0.0.1', 8080)>)
  • self.client_address即客户端地址。

6.1 ThreadingTCPServer

ThreadingTCPServer实现的Soket服务器内部会为每个client创建一个 “线程”,该线程用来和客户端进行交互。

使用ThreadingTCPServer:

  • 创建一个继承自 SocketServer.BaseRequestHandler 的类
  • 类中必须定义一个名称为 handle 的方法
  • 启动ThreadingTCPServer。
  • 启动serve_forever() 链接循环

服务端:

import socketserverclass MyServer(socketserver.BaseRequestHandler):def handle(self):conn = self.request# print(addr)conn.sendall("欢迎致电10086,请输入1XXX,0转人工服务。".encode("utf-8"))Flag = Truewhile Flag:data = conn.recv(1024).decode("utf-8")if data == "exit":Flag = Falseelif data == '0':conn.sendall("您的通话可能会被录音。。。".encode("utf-8"))else:conn.sendall("请重新输入。".encode('utf-8'))if __name__ == '__main__':server = socketserver.ThreadingTCPServer(("127.0.0.1",60000),MyServer)server.serve_forever()  #内部实现while循环监听是否有客户端请求到达。

客户端:

import socketip_port = ('127.0.0.1',60000)
sk = socket.socket()
sk.connect(ip_port)
sk.settimeout(5)while True:data = sk.recv(1024).decode("utf-8")print('receive:',data)inp = input('please input:')sk.sendall(inp.encode('utf-8'))if inp == 'exit':break
sk.close()

七、基于UDP的套接字

  • recvfrom(buffersize[, flags])接收消息,buffersize是一次接收多少个字节的数据。
  • sendto(data[, flags], address) 发送消息,data是要发送的二进制数据,address是要发送的地址,元组形式,包含IP和端口

服务端:

from socket import *
s=socket(AF_INET,SOCK_DGRAM)  #创建一个基于UDP的服务端套接字,注意使用SOCK_DGRAM类型
s.bind(('127.0.0.1',8080))  #绑定地址和端口,元组形式while True:    #通信循环client_msg,client_addr=s.recvfrom(1024) #接收消息print(client_msg)s.sendto(client_msg.upper(),client_addr) #发送消息

客户端:

from socket import *
c=socket(AF_INET,SOCK_DGRAM)   #创建客户端套接字while True:msg=input('>>: ').strip()c.sendto(msg.encode('utf-8'),('127.0.0.1',8080)) #发送消息server_msg,server_addr=c.recvfrom(1024) #接收消息print('from server:%s msg:%s' %(server_addr,server_msg))

模拟即时聊天
由于UDP无连接,所以可以同时多个客户端去跟服务端通信

服务端:

from socket import *server_address = ("127.0.0.1",60000)
udp_server_sock = socket(AF_INET,SOCK_DGRAM)
udp_server_sock.bind(server_address)while True:qq_msg,addr = udp_server_sock.recvfrom(1024)print("来自[%s:%s]的一条消息:\033[32m%s\033[0m"%(addr[0],addr[1],qq_msg.decode("utf-8")))back_msg = input("回复消息:").strip()udp_server_sock.sendto(back_msg.encode("utf-8"),addr)udp_server_sock.close()

客户端:

from socket import *BUFSIZE = 1024
udp_client_sock = socket(AF_INET,SOCK_DGRAM)
qq_name_dic = {"alex":("127.0.0.1",60000),"egon":("127.0.0.1",60000),"seven":("127.0.0.1",60000),"yuan":("127.0.0.1",60000),
}while True:qq_name = input("请选择聊天对象:").strip()while True:msg = input("请输入消息,回车发送:").strip()if msg == "quit":breakif not msg or not qq_name or qq_name not in qq_name_dic:continueprint(msg,qq_name_dic[qq_name])udp_client_sock.sendto(msg.encode("utf-8"),qq_name_dic[qq_name])back_msg,addr = udp_client_sock.recvfrom(BUFSIZE)print("来自[%s:%s]的一条消息:\033[32m%s\033[0m" %(addr[0],addr[1],back_msg.decode("utf-8")))
udp_client_sock.close()

注意:
1.你单独运行上面的udp的客户端,你发现并不会报错,相反tcp却会报错,因为udp协议只负责把包发出去,对方收不收,我根本不管,而tcp是基于链接的,必须有一个服务端先运行着,客户端去跟服务端建立链接然后依托于链接才能传递消息,任何一方试图把链接摧毁都会导致对方程序的崩溃。

2.上面的udp程序,你注释任何一条客户端的sendinto,服务端都会卡住,为什么?因为服务端有几个recvfrom就要对应几个sendinto,哪怕是sendinto(b'')那也要有。

3.recvfrom(buffersize)如果设置每次接收数据的字节数,小于对方发送的数据字节数,如果运行Linux环境下,则只会接收到recvfrom()所设置的字节数的数据;而如果运行windows环境下,则会报错。

基于socketserver实现多线程的UDP服务端:

import socketserverclass MyUDPhandler(socketserver.BaseRequestHandler):def handle(self):client_msg,s=self.requests.sendto(client_msg.upper(),self.client_address)if __name__ == '__main__':s=socketserver.ThreadingUDPServer(('127.0.0.1',60000),MyUDPhandler)s.serve_forever()

转载于:https://www.cnblogs.com/caigy/p/7220954.html

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 蓝懿教育九月十五日记录

    1.创建文本方法一:直接拖拽方法二:代码创建UITextField *tf=[[UITextField alloc]initWithFrame:CGRectMake(0,0,100,100)];2.文本的设置1、设置文本边框样式2、文本字体颜色3、文本字体大小4、设置文本行数5、文本背景颜色6、文本居中7、文本placeholder占位符:文本的提示3…...

    2024/4/13 3:05:45
  2. android 常用第三方插件收藏

    1、android-vertical-slide-view : 仿照淘宝和聚美优品,在商品详情页,向上拖动时,可以加载下一页。使用ViewDragHelper,滑动比较流畅。2、Android-MaterialRefreshLayout :这是一个下拉刷新控件,它比SwipeRefreshLayout更漂亮和强大3、TwinklingRefreshLayout(推荐使用…...

    2024/4/12 23:20:21
  3. 配置电子邮件传输

    基本电子邮件发送 电子邮件发送: 服务器使用SMTP协议将电子邮件提交至TCP端口25,或由本地客户端通过/usr/bin/sendmail程序进行提交。如果该MTA是最终目标位置,邮件将传递至MDA。否则,将使用MX记录在DNS中查找下一个MTA,并使用SMTP进转发。 MDA:“邮件发送代理”。MDA将邮件发…...

    2024/4/12 23:22:54
  4. python机器学习库sklearn——SGD梯度下降法

    分享一个朋友的人工智能教程。零基础!通俗易懂!风趣幽默!还带黄段子!大家可以看看是否对自己有帮助:点击打开 docker/kubernetes入门视频教程全栈工程师开发手册 (作者:栾鹏)python数据挖掘系列教程梯度下降法算法详情参考:https://www.cnblogs.com/pinard/p/5970503.…...

    2024/4/19 17:17:01
  5. FreeBSD网络相关命令

    23.5.1 ping 检查远程系统的联机状态。ping 指令会送出 ICMP 封包到指定的主机,我们可以藉此来检查网络联机品质。 常用参数如下: 参数 说明 -c count 指定要计算 count 次。 -s size 指定每个封包大小为 size。 -t timeout 指定 time out 时间。 -I interface 如果目标主机地…...

    2024/4/17 9:36:10
  6. Apache - 模块 - mod_rewrite - RewriteCond - 以域名为重写条件

    Apache - 模块 - mod_rewrite - RewriteCond - 以域名为重写条件 虚拟主机绑定了两个域名,假设为www.a.com和www.b.com,希望访问www.a.com时按正常方式处理,访问www.b.com时显示/b/index.html的内容。[示例] <IfModule rewrite_module> RewriteEngine on RewriteCond …...

    2024/4/12 23:19:56
  7. Lenovo X240/250锁定 Fn 键时 End 键工作不正常

    Lenovo ThinkPadX240和X250,这两个机型的End键有点与众不同:在默认Fn键锁定的情况下,在普遍的编辑文档时,按Home键能正常返回行首,而End却并不会跑到行尾,只有取消Fn锁定才会有正常的效果。Fn键锁定时,Insert键也不能正常工作。这些是ThinkPad X240和X250设计的BUG。解决…...

    2024/4/12 23:20:06
  8. Android版本信息查询

    查询Android系统的VERSION,API LEVEL,BUILD TYPE查询方式: 命令行方式使用工具: adbadb shell getprop ro.build.version.release adb shell getprop ro.build.version.sdk adb shell getprop ro.build.type...

    2024/4/18 9:53:00
  9. 【Struts2五】ValueStack以及ognl表达式二(常用标签)

    Ognl常用标签: 1、s:debug如果把该标签放入到s:iterator中可以看到当前正在迭代的元素的状态2、s:property1、输出2、value属性:值能直接跟ognl表达式3、如果value属性不写,则默认输出栈顶的元素 3:s:iterator标签(1)、iterator迭代Collection,Map,Object[],还可以以…...

    2024/4/8 21:06:42
  10. Apache - mod_rewrite - 与PHP集成时不需要对$_GET额外处理

    Apache - mod_rewrite - 与PHP集成时不需要对$_GET额外处理 [.htaccess文件] RewriteEngine on RewriteRule ^info/(/d+)-(/d+)-(/d+)/.html dispbbs.php/?boarid=$1/&id=$2/&page=$3 [L][dispbbs.php文件] <?php var_dump($_GET); ?> 访问如下URL htt…...

    2024/4/12 23:20:47
  11. Linux网络命令--mail邮件命令

    Linux网络命令--mail邮件命令 1 命令说明 指令名称:mail 指令所在路径:/bin/mail 执行权限:所有用户 语法:mail [用户名] 此处的用户无论当前是否在线都可以 功能描述:查看发送电子邮件 范例:#mail root 1.2 应用举例 1.2.1 给root用户发送邮件 命令演示: [root@l…...

    2024/4/12 23:21:17
  12. python实现机器学习中的各种距离计算及文本相似度算法

    在自然语言处理以及机器学习的分类或者聚类中会涉及到很多距离的使用,各种距离的概念以及适用范围请自行百度或者参考各种距离 import numpy as np import math# 依赖包numpy、python-Levenshtein、scipydef Euclidean(vec1, vec2):npvec1, npvec2 = np.array(vec1), np.array…...

    2024/4/12 23:20:52
  13. Apache - 模块 - mod_rewrite - RewriteRule - 示例

    Apache - 模块 - mod_rewrite - RewriteRule - 示例 http://blog.csdn.net/hu_zhenghui/play.php?type=movie&id=10转换成http://blog.csdn.net/hu_zhenghui/movie/10/[代码]<IfModule rewrite_module>RewriteEngine onRewriteRule ^([^/]+)/(/d+)/$ play.php?type…...

    2024/4/19 9:21:57
  14. IOS动画与绘图

    目录:UIView动画 子视图翻转动画UIImageView动画CATransition动画Core Graphics绘图:(线、矩形、曲线、文字、图片)CALayer 核心动画:(关键帧动画、单一动画、组动画)1.UIView动画(1)设置代理(viewController)动画开始: beginAnimations设置动画加减速方式: setAnima…...

    2024/4/12 23:21:02
  15. python机器学习库sklearn与graphlab与opencv的安装

    1.安装python首先安装python3.5 Link 然后安装python2.7 Link 注意选择合适的版本(记得之后安装的GraphLab只能用64位,也只能使用python2)2.安装使用sklearn库sklearn Link安装好python后,使用pip2和pip3两个命令去区分安装库到那个python版本中 如安装python2.7的scipy…...

    2024/4/8 21:06:36
  16. 关于StringUtils的各种方法的功能、解析

    这些是个人,为了加强StringUtils方法的记忆,随便写写的,希望大家喜欢 public class StringUtilsTest extends TestCase {// 将字符串转换成小写 @Testpublic void test_lowerCase() {// assertEquals("aaa", StringUtils.lowerCase("AAA")); // assertE…...

    2024/4/12 23:21:07
  17. AndroidTV中利用MainUpView实现飞框选中效果

    相信很多从事AndroidTV开发的朋友都对如何展示item的选中效果感到苦恼,电视端开发与移动端最大的不同是用户只能通过一个遥控器进行控制(当然如果你的电视是触屏的话除外……),在这个时候,我们需要让用户知道当前选中的到底是哪一个项目,通常来说,有几种常见的实现方法:…...

    2024/4/12 23:21:02
  18. Apache - 模块 - mod_rewrite - RewriteRule - 匹配任意字符串时,可以借助正则表达式的灵活特性

    Apache - 模块 - mod_rewrite - RewriteRule - 匹配任意字符串时,可以借助正则表达式的灵活特性 通常情况下,在RewriteRule中的正则表达式需要考虑在前面加上^(匹配字符串开头)以及在末尾加上$(匹配字符串末尾),如果在前面不增加^用于匹配字符串开头的话,例如: Rewri…...

    2024/4/19 8:26:59
  19. ubuntu 检测网络状态命令

    一、检测网络状态常用的基本命令1)ifconfig命令被用于配置和显示Linux内核中网络接口的网络参数ifconfig输出结果如下:drew@ubun:~$ ifconfig eth0 Link encap:以太网 硬件地址 d0:17:c2:aa:11:b8 inet 地址:10.0.4.33 广播:10.0.31.255 掩码:255.255.224.0inet6 地址…...

    2024/4/12 23:21:07
  20. MATLAB,R语言、Python、Java,到底哪种语言学机器学习最靠谱?

    China+AI已是大势所趋,吸引的不仅是大公司,更有许多人工智能的爱好者。曾经只存在于科幻小说或实验室中遥不可及的人工智能产品也逐渐成为老百姓日常生活的一部分。而支撑人工智能技术迅速发展的核心技术之一就是机器学习。 最近几年,人工智能浪潮层层推进,对各大科技公司产…...

    2024/4/19 0:12:52

最新文章

  1. js字符串方法总结_js 字符串方法(1)

    var count0 var prosstr.indexOf(a) while(pros!-1) {countprosstr.indexOf(a,pros1) } console.log(count);3. chartAt() 返回指定位置的字符 根据下标获取字符var strabcdef console.log(str.charAt(2));4. lastIndexOf() 返回字符串字串出现的最后一处出现的位置索引 没有匹…...

    2024/4/27 8:28:07
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. K8S容器空间不足问题分析和解决

    如上图&#xff0c;今天测试环境的K8S平台出现了一个问题&#xff0c;其中的一个容器报错&#xff1a;Free disk space below threshold. Available: 3223552 bytes (threshold: 10485760B)&#xff0c;意思服务器硬盘空间不够了。这个问题怎么产生的&#xff0c;又怎么解决的呢…...

    2024/4/23 6:25:22
  4. Vue ts 如何给 props 中的变量指定特定类型,比如 Interface 类的

    Vue ts 如何给 props 中的变量指定特定类型&#xff0c;比如 Interface 类的 我有一个这样的变量值类型 一、在没用 ts 之前的 props 类型指定方式 我们都知道之前在没用 ts 之前的 props 变量值类型指定方式&#xff1a; 如下图&#xff0c;billFood 定义方式是这样的&…...

    2024/4/26 23:31:20
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/4/26 18:09:39
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/4/26 20:12:18
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/26 23:05:52
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/4/27 4:00:35
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/25 18:39:22
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/25 18:39:22
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/26 21:56:58
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/25 16:48:44
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/26 16:00:35
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/4/25 18:39:16
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/25 18:39:16
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/26 22:01:59
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/25 18:39:14
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/4/26 23:04:58
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/25 2:10:52
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/25 18:39:00
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/26 19:46:12
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/4/25 18:38:58
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/25 18:38:57
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57