1. GIL(Global Interpreter Lock)
-
说明:在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势
-
描述:GIL的本质是互斥锁,将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全;保护不同的数据的安全,就应该加不同的锁
-
介绍:所有数据都是共享的,这其中,代码作为一种数据也是被所有线程共享的;所有线程的任务,都需要将任务的代码当做参数传给解释器的代码去执行,即所有的线程要想运行自己的任务,首先需要解决的是能够访问到解释器的代码。多个线程先访问到解释器的代码,即拿到执行权限,然后将target的代码交给解释器的代码去执行解释器的代码是所有线程共享的,所以垃圾回收线程也可能访问到解释器的代码而去执行,这会导致同一个数据正在被运行的同时也会被回收,解决这种问题的方式就是枷锁处理。GIL,就是保证Python解释器在同一时间只能执行一个任务的代码
-
GIL与Lock的区别:
-
GIL保护的是解释器级的数据
-
Lock保护的是文件级的数据
-
GIL的应用场景:
-
每一个Cpython进程内都有一个GIL
-
GIL导致同一个进程内的多个线程同一时间只能有一个运行
-
GIL是存在是因为Cpython的内存管理不是线程安全的
-
对于计算型密集型使用多进程
-
对于I/O密集型使用多线程
2. 多线程性能测试
-
单核情况下:
-
计算密集型的任务,使用多线程的方式效率高
-
I/O密集型的任务,使用多线程的方式效率高
-
多核情况下:
-
计算密集型的任务,使用多进程的方式效率高
-
I/O密集型的任务,使用多线程的方式效率高
# 计算密集型
import os
import time
from multiprocessing import Process
from threading import Thread
def task():
res = 0
for i in range(10000000):
res *= i
if __name__ == '__main__':
print('本机cpu核心数是[%s]核' % os.cpu_count())
print('计算密集型...')
p_list = []
t_list = []
p_start_time = time.time()
for i in range(10):
p = Process(target=task)
p.start()
p_list.append(p)
for j in p_list:
p.join()
print('多进程的运行时间:%s' % (time.time() - p_start_time))
t_start_time = time.time()
for i in range(10):
t = Thread(target=task)
t.start()
t_list.append(t)
for j in t_list:
t.join()
print('多线程的运行时间:%s' % (time.time() - t_start_time))
print('yan...')
>>>
本机cpu核心数是[4]核
计算密集型...
多进程的运行时间:4.771674394607544
多线程的运行时间:6.147441625595093
yan...
# I/O密集型
import os
import time
from multiprocessing import Process
from threading import Thread
def task():
time.sleep(2)
if __name__ == '__main__':
print('本机cpu核心数是[%s]核' % os.cpu_count())
print('I/O密集型...')
p_list = []
t_list = []
p_start_time = time.time()
for i in range(10):
p = Process(target=task)
p.start()
p_list.append(p)
for j in p_list:
p.join()
print('多进程的运行时间:%s' % (time.time() - p_start_time))
t_start_time = time.time()
for i in range(10):
t = Thread(target=task)
t.start()
t_list.append(t)
for j in t_list:
t.join()
print('多线程的运行时间:%s' % (time.time() - t_start_time))
print('yan...')
>>>
本机cpu核心数是[4]核
I/O密集型...
多进程的运行时间:2.6464638710021973
多线程的运行时间:2.0020933151245117
yan...
3. 死锁现象和递归锁
-
死锁:两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去,这些永远在互相等待的进程或线程称为死锁
# 死锁的例子
from threading import Thread, Lock
mutex1 = Lock()
mutex2 = Lock()
class Mythread(Thread):
def run(self):
self.task1()
self.task2()
def task1(self):
mutex1.acquire()
print('%s ---> 锁1' % self.name)
mutex2.acquire()
print('%s ---> 锁2' % self.name)
mutex2.release()
mutex1.release()
def task2(self):
mutex2.acquire()
print('%s ---> 锁2' % self.name)
mutex1.acquire()
print('%s ---> 锁1' % self.name)
mutex1.release()
mutex2.release()
if __name__ == '__main__':
for i in range(10):
t = Mythread()
t.start()
>>>
Thread-1 ---> 锁1
Thread-1 ---> 锁2
Thread-1 ---> 锁2
Thread-2 ---> 锁1
-
递归锁:解决死锁的方式,可重入锁(RLock)
-
RLock:可重入锁;内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源;上例中RLock代替Lock,则不会发生死锁
from threading import Thread, RLock
mutex1 = mutex2 = RLock()
class Mythread(Thread):
def run(self):
self.task1()
self.task2()
def task1(self):
mutex1.acquire()
print('%s ---> 锁1' % self.name)
mutex2.acquire()
print('%s ---> 锁2' % self.name)
mutex2.release()
mutex1.release()
def task2(self):
mutex2.acquire()
print('%s ---> 锁2' % self.name)
mutex1.acquire()
print('%s ---> 锁1' % self.name)
mutex1.release()
mutex2.release()
if __name__ == '__main__':
for i in range(10):
t = Mythread()
t.start()
>>>
Thread-1 ---> 锁1
Thread-1 ---> 锁2
Thread-1 ---> 锁2
Thread-1 ---> 锁1
Thread-2 ---> 锁1
Thread-2 ---> 锁2
Thread-2 ---> 锁2
Thread-2 ---> 锁1
Thread-3 ---> 锁1
Thread-3 ---> 锁2
Thread-3 ---> 锁2
Thread-3 ---> 锁1
Thread-5 ---> 锁1
Thread-5 ---> 锁2
Thread-5 ---> 锁2
Thread-5 ---> 锁1
Thread-6 ---> 锁1
Thread-6 ---> 锁2
Thread-6 ---> 锁2
Thread-6 ---> 锁1
Thread-7 ---> 锁1
Thread-7 ---> 锁2
Thread-7 ---> 锁2
Thread-7 ---> 锁1
Thread-4 ---> 锁1
Thread-4 ---> 锁2
Thread-4 ---> 锁2
Thread-4 ---> 锁1
Thread-10 ---> 锁1
Thread-10 ---> 锁2
Thread-10 ---> 锁2
Thread-10 ---> 锁1
Thread-9 ---> 锁1
Thread-9 ---> 锁2
Thread-8 ---> 锁1
Thread-8 ---> 锁2
Thread-8 ---> 锁2
Thread-8 ---> 锁1
Thread-9 ---> 锁2
Thread-9 ---> 锁1
4. 定时器
-
描述:指定时间后执行的操作;类似time.sleep(n)休眠n秒的时间后在运行下面的程序代码
-
使用定时器需要导入模块:from threading import Timer
-
参数:Timer(n, func, args)
-
n秒后运行函数func
-
args和kwargs指定传入func的参数,元组的形式指定位置传参,字典的形式指定关键字传参
-
方法:
-
start():启动定时器线程
-
join():等待定时器线程运行结束
-
cancel():取消定时器线程
import time
from threading import Timer
def task(name, age, sex='girl', job='IT'):
print('name ---> %s' % name)
print('age ---> %s' % age)
print('sex ---> %s' % sex)
print('job ---> %s' % job)
print('yan...')
t = Timer(2, task, args=('yy', 18), kwargs={'job': 'Admin'})
start_time = time.time()
t.start()
t.join()
print(time.time() - start_time)
>>>
yan...
name ---> yy
age ---> 18
sex ---> girl
job ---> Admin
2.0005106925964355
5. 协程
-
引子:cpu正在运行一个任务,会在两种情况下切走去执行其他的任务(切换由操作系统强制控制),一种情况是该任务发生了阻塞,另外一种情况是该任务计算的时间过长。对于单线程下,我们不可避免程序中出现io操作,但如果我们能在自己的程序中(即用户程序级别,而非操作系统级别)控制单线程下的多个任务能在一个任务遇到io阻塞时就切换到另外一个任务去计算,这样就保证了该线程能够最大限度地处于就绪态,即随时都可以被cpu执行的状态,相当于我们在用户程序级别将自己的io操作最大限度地隐藏起来,从而可以迷惑操作系统,让其看到:该线程好像是一直在计算,io比较少,从而更多的将cpu的执行权限分配给我们的线程
-
本质:在单线程下,由用户自己控制一个任务遇到io阻塞了就切换另外一个任务去执行,以此来提升效率。为了实现它,我们需要找寻一种可以同时满足以下条件的解决方案:
-
可以控制多个任务之间的切换,切换之前将任务的状态保存下来,以便重新运行时,可以基于暂停的位置继续执行
-
可以检测io操作,在遇到io操作的情况下才发生切换
-
说明:单线程下实现并发,又叫微线程、纤程,英文名Coroutine。协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的
-
实现方式:切换+保存状态
-
强调:
-
python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
-
单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(非io操作的切换与效率无关)
-
优点:
-
协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
-
单线程内就可以实现并发的效果,最大限度地利用cpu
-
缺点:
-
协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
-
协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程
-
特点:
-
必须在只有一个单线程里实现并发
-
修改共享数据不需加锁
-
用户程序里自己保存多个控制流的上下文栈
-
附加特点:一个协程遇到I/O操作自动切换到其它协程(yield和greenlet都无法实现检测I/O,可以使用gevent模块(select机制))
6. gevent模块
from gevent import monkey;monkey.patch_all()
import time
import random
import gevent
from threading import current_thread
def eat():
print('eat ---> food...')
time.sleep(random.randint(1, 3))
print('eat ---> fruit...')
return current_thread().getName()
def drink():
print('drink ---> water...')
time.sleep(random.randint(1, 3))
print('drink ---> Cola...')
return current_thread().getName()
def play():
print('play ---> games...')
time.sleep(random.randint(1, 3))
print('play ---> balls...')
return current_thread().getName()
g1 = gevent.spawn(eat)
g2 = gevent.spawn(drink)
g3 = gevent.spawn(play)
# g1.join()
# g2.join()
# g3.join()
gevent.joinall([g1, g2, g3])
gevent.sleep(2)
print(g1.value)
print(g2.value)
print(g3.value)
time.sleep(1)
print('yan...')
>>>
eat ---> food...
drink ---> water...
play ---> games...
drink ---> Cola...
play ---> balls...
eat ---> fruit...
DummyThread-3
DummyThread-1
DummyThread-2
yan...
7. 应用
from gevent import monkey; monkey.patch_all()
import time
import gevent
from threading import current_thread
def task():
time.sleep(1)
print('[%s] is done...' % current_thread().getName())
def synchronous():
synchronous_start_time = time.time()
for i in range(10):
g = gevent.spawn(task)
g.join()
print('用时:%s' % (time.time() - synchronous_start_time))
def asynchronous():
asynchronous_start_time = time.time()
g_list = []
for i in range(10):
g = gevent.spawn(task)
g_list.append(g)
gevent.joinall(g_list)
print('用时:%s' % (time.time() - asynchronous_start_time))
if __name__ == '__main__':
print('synchronous:')
synchronous()
print('-' * 20)
print('asynchronous:')
asynchronous()
>>>
synchronous:
[DummyThread-1] is done...
[DummyThread-2] is done...
[DummyThread-3] is done...
[DummyThread-4] is done...
[DummyThread-5] is done...
[DummyThread-6] is done...
[DummyThread-7] is done...
[DummyThread-8] is done...
[DummyThread-9] is done...
[DummyThread-10] is done...
用时:10.008307695388794
--------------------
asynchronous:
[DummyThread-11] is done...
[DummyThread-12] is done...
[DummyThread-13] is done...
[DummyThread-14] is done...
[DummyThread-15] is done...
[DummyThread-16] is done...
[DummyThread-17] is done...
[DummyThread-18] is done...
[DummyThread-19] is done...
[DummyThread-20] is done...
用时:1.0008456707000732
from gevent import monkey; monkey.patch_all()
import time
import gevent
import requests
def get_page(url):
print('get %s' % url)
response = requests.get(url)
if response.status_code == 200:
print('%s的页面大小是:%s' % (url, len(response.text)))
if __name__ == '__main__':
urls = [
'http://www.baidu.com',
'http://www.tmall.com',
'http://www.jd.com',
'http://www.iqiyi.com',
'http://www.youku.com'
]
start_time = time.time()
g_list = []
for i in urls:
g = gevent.spawn(get_page, i)
g_list.append(g)
gevent.joinall(g_list)
print('用时:%s' %(time.time() - start_time))
>>>
get http://www.baidu.com
get http://www.tmall.com
get http://www.jd.com
get http://www.iqiyi.com
get http://www.youku.com
http://www.baidu.com的页面大小是:2381
http://www.iqiyi.com的页面大小是:214584
http://www.youku.com的页面大小是:686324
http://www.tmall.com的页面大小是:215146
http://www.jd.com的页面大小是:129123
用时:0.8404057025909424
# 服务端
from gevent import monkey; monkey.patch_all()
import gevent
import subprocess
import json
import struct
from socket import *
def server_socket(ip, port):
server = socket(AF_INET, SOCK_STREAM)
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
server.bind((ip, port))
server.listen(5)
while True:
conn, add = server.accept()
print('客户端[%s]成功连接,端口是[%s]' % (add[0], add[1]))
gevent.spawn(shell_result, conn)
server.close()
def shell_result(conn):
while True:
try:
shell = conn.recv(1024)
if not shell:
break
obj = subprocess.Popen(shell.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout = obj.stdout.read()
stderr = obj.stderr.read()
header_dict = {'total_size': len(stdout) + len(stderr)}
header_json = json.dumps(header_dict)
header_bytes = header_json.encode('utf-8')
header_size = struct.pack('i', len(header_bytes))
conn.send(header_size)
conn.send(header_bytes)
conn.send(stdout)
conn.send(stderr)
except ConnectionResetError:
break
conn.close()
if __name__ == '__main__':
ip = '127.0.0.1'
port = 8080
server_socket(ip, port)
# 客户端1
import json
import struct
from socket import *
ip = '127.0.0.1'
port = 8080
client = socket(AF_INET, SOCK_STREAM)
client.connect((ip, port))
def shell_run(shell):
client.send(shell.encode('utf-8'))
header_len = client.recv(4)
header_size = struct.unpack('i', header_len)[0]
header_bytes = client.recv(header_size)
header_json = header_bytes.decode('utf-8')
header_dict = json.loads(header_json)
total_size = header_dict['total_size']
recv_size = 0
data = b''
while recv_size < total_size:
recv_data = client.recv(1024)
data += recv_data
recv_size += len(recv_data)
print(data.decode('gbk'))
if __name__ == '__main__':
while True:
shell = input('请输入需要执行的系统命令:').strip()
if not shell:
continue
shell_run(shell)
client.close()
# 客户端2
import json
import struct
from socket import *
ip = '127.0.0.1'
port = 8080
client = socket(AF_INET, SOCK_STREAM)
client.connect((ip, port))
def shell_run(shell):
client.send(shell.encode('utf-8'))
header_len = client.recv(4)
header_size = struct.unpack('i', header_len)[0]
header_bytes = client.recv(header_size)
header_json = header_bytes.decode('utf-8')
header_dict = json.loads(header_json)
total_size = header_dict['total_size']
recv_size = 0
data = b''
while recv_size < total_size:
recv_data = client.recv(1024)
data += recv_data
recv_size += len(recv_data)
print(data.decode('gbk'))
if __name__ == '__main__':
while True:
shell = input('请输入需要执行的系统命令:').strip()
if not shell:
continue
shell_run(shell)
client.close()