python进程与线程之间的区别

目录

  • 一.GIL与普通互斥锁的区别
  • 1.GIL锁
  • 2.GIL与互斥锁
  • 二.验证多线程的作用
  • 1.多核计算密集型任务多线程
  • 2.多核计算密集型任务多进程
  • 结论
  • 3.单核IO密集型任务多线程
  • 4.单核IO密集型任务多进程
  • 结论
  • 三.死锁现象
  • 死锁现象
  • 四.信号量
  • 1.多把互斥锁
  • 五.event事件
  • 1.子线程控制子线程运行
  • 六.进程池与线程池
  • 七.协程
  • 1.协程演练
  • 八.基于协程实现TCP服务端并发
  • 1.服务端
  • 2.客户端

一.GIL与普通互斥锁的区别

# 1.首先什么是GIL
GIL是全局解释器锁  解决解释器中多个线程资源竞争问题(多个子线程在系统资源竞争
都在等待对象某个部分资源解除占用状态.结果谁也不愿意先解锁 然后互相等待 导致
程序无法进行下去)

# 2.使用多线程时
当我们使用多线程的时候 每一个进程中只有一个GIL锁那么这个线程中谁拿到了这个GIL锁
那么谁就可以使用CPU(多个进程内有多个GIL锁.但每个进程中只有一个GIL锁)所以当python用
Cpython作为解释器的时候,多线程就不是真正意义上的多线程,属于一个伪并发的多线程

# 3.解释器Cpython(使用率挺高)
在Cpython解释器下 GIL导致了同一进程下的多个线程无法利用多核

# 4.GIL与普通互斥锁的区别
GIL锁:保证了同一时刻 只有有一个线程可以使用到CPU
互斥锁:多线程时,保证修改共享数据时有序的修改,不会产生数据修改混乱

1.GIL锁

from threading import Thread

choice = 50

def choice_task():
    global choice
    choice -= 1

# 创建50个线程 在内存也就是一个进程内同时启动50个线程
for i in range(50):
    q = Thread(target=choice_task)
    q.start()
print(choice)
"""
总结下:GIL的全局解释器锁
解决了多个线程纠纷资源问题
结果为:0 释放一个接收一个重复50次
"""

2.GIL与互斥锁

from threading import Thread,Lock
import time

choice = 100
t = Lock()

def choice_task():
    global choice
    t.acquire()   # 加锁
    i = choice
    time.sleep(0.1)  # io 操作
    choice = i - 1
    t.release()  # 释放锁

q_list = []
for j in range(100):
    q = Thread(target=choice_task)
    q.start()
    q_list.append(q)
for w in q_list:
    q.join()
print(choice)
"""
个人总结:
当线程碰到io操作的时候那么python解释器锁已经没做用了
因为io操作结束后自动解除锁 子线程就又会从新回到就绪太
创建100个线程进程同时进行 100-1
结果是99

如果加上join那么主线程会等子线程结束后执行那么结果为0
添加for循环打印的话没有join方法那么就会出现 99个 100-1
因为主线程未等待子线程直接打印添加列表内的数值

区分GIL锁 与 互斥锁 根据不同的数据使用不同的锁
"""

二.验证多线程的作用

通过CPU的个数(核) 和 任务的数据类型

# 单核(CPU)
# 多个IO密集型任务
多进程:浪费资源 无法利用多个CPU
多线程:节省资源 切换 保存

# 多个计算密集型任务
多进程:损耗时间更长长 创建损耗和切换损耗
多线程:损耗时间较短短 切换损耗较短

# 多核(CPU)
# 多余个IO密集型任务
多进程:浪费资源 多核无用户处
多线程:节省资源 切换 保存

# 多个计算密集型任务
多进程:完美利用多核优势 速度更快
多线程:速度相比之下比较缓慢

1.多核计算密集型任务多线程

from threading import Thread
from multiprocessing import Process
import time

def work():
    res = 1
    for i in range(1, 10000):
        res *= i


if __name__ == '__main__':
    # print(os.cpu_count())  #   查看当前计算机CPU个数
    start_time = time.time()
    t_list = []
    for i in range(4):
        t = Thread(target=work)
        t.start()
        t_list.append(t)
    for t in t_list:
        t.join()
    print('总耗时:%s' % (time.time() - start_time))

结果为 多线程:0.28725099563598633

2.多核计算密集型任务多进程

from threading import Thread
from multiprocessing import Process
import time

def work():
    res = 1
    for i in range(1, 10000):
        res *= i


if __name__ == '__main__':
    # print(os.cpu_count())  #   查看当前计算机CPU个数
    start_time = time.time()
    t_list = []
    for i in range(4):
        t = Process(target=work)      # Process进程 Thread线程区分
        t.start()
        t_list.append(t)
    for t in t_list:
        t.join()
    print('总耗时:%s' % (time.time() - start_time))

结果:多进程 0.08273792266845703

结论

在计算密集型任务下 多进程 要比 多线型快不少
但如果你计算机核少于4核或等于4核那么可能会恰恰相反

3.单核IO密集型任务多线程

from threading import Thread
import time


def work():
    time.sleep(2)   # 纯IO操作



if __name__ == '__main__':
    start_time = time.time()
    p_list = []
    for i in range(10):
        p = Thread(target=work)
        p.start()
        p_list.append(p)
    for p in p_list:
        p.join()
    print('总耗时:%s' % (time.time() - start_time))

结果 总耗时:2.0170059204101562 并没等子线程循环反应过来 就已经结束掉了

4.单核IO密集型任务多进程

from multiprocessing import Process
import time


def work():
    time.sleep(2)   # 纯IO操作



if __name__ == '__main__':
    start_time = time.time()
    p_list = []
    for i in range(10):
        p = Process(target=work)
        p.start()
        p_list.append(p)
    for p in p_list:
        p.join()
    print('总耗时:%s' % (time.time() - start_time))

结果:总耗时:总耗时:4.950006723403931 等子进程打印完后才才结束

结论

在多核IO密集型任务多进程与多线程 对比下 多线程快于多进程
总体来说是多线程比多进程快

三.死锁现象

# 什么是死锁现象 频繁的加锁 解锁

死锁现象

from threading import Thread, Lock
import time

# 产生两把(复习 面向对象和单例模式):每天都可以写写单例啊 算法啊...
mutexA = Lock()
mutexB = Lock()


class MyThread(Thread):
    def run(self):
        self.f1()
        self.f2()
    def f1(self):
        mutexA.acquire()              # 抢锁
        print(f'{self.name}抢到了A锁')
        mutexB.acquire()              # 抢锁
        print(f'{self.name}抢到了B锁')
        mutexB.release()              # 解锁
        mutexA.release()              # 解锁
    def f2(self):
        mutexB.acquire()
        print(f'{self.name}抢到了B锁')
        time.sleep(2)
        mutexA.acquire()
        print(f'{self.name}抢到了A锁')
        mutexA.release()
        mutexB.release()

for i in range(2):或(100)  循环减少不会死锁 
    t = MyThread()
    t.start()

结果: 结果就是卡在打印界面 运行的太快锁没来得及上就又解锁 导致卡主 由此可以引出锁不能轻易使用

四.信号量

信号量在不同的知识体系中 展示出来的功能是不一样的
# 1.在并发编程中
信号量意思是多把互斥锁

# 2.在django框架中
信号量意思是达到某个条件自动触发特定功能

1.多把互斥锁

from threading import Thread, Semaphore
import time
import random

sp = Semaphore(5)  # 这个方法是可以管理有几个锁


def task(name):
    sp.acquire()  # 抢5个锁
    print('%s次' % name)
    time.sleep(random.randint(1, 5))
    sp.release()  # 放锁


for i in range(1, 10):
    t = Thread(target=task, args=('学生上课打瞌睡%s' % i, ))
    t.start()

结果: 学生上课打瞌睡1次
学生上课打瞌睡2次
学生上课打瞌睡3次
学生上课打瞌睡4次
学生上课打瞌睡5次
学生上课打瞌睡6次
学生上课打瞌睡7次
学生上课打瞌睡9次
学生上课打瞌睡8次

五.event事件

event事件:子线程运行可以由子线程来决定
# 其实可以通过Queue队列方法实现

1.子线程控制子线程运行

from threading import Thread, Event
import time

event = Event()  # 子类控制子类运行方法


def light():
    print('红灯亮着的 所有人都不能动')
    time.sleep(3)
    print('绿灯亮了 油门踩到底 给我冲!!!')
    event.set()


def car(name):
    print('%s正在等红灯' % name)
    event.wait()
    print('%s加油门 飙车了' % name)


t = Thread(target=light)
t.start()
for i in range(20):
    t = Thread(target=car, args=('平板双喷卡丁车%s' %  i,))
    t.start()

六.进程池与线程池

# 服务端必备的三要素:想象为游戏

不轻易断开链接:除了更新想什么时候玩都型
固定ip与port :登陆账号密码就能随便玩
支持高并发    : 一堆朋友一起玩

# TCP服务端实现并发
多进程:一个客户端对应一个进程
多线程:一个客户端对应一个线程

# 进程与线程'池'
池:保证计算机硬件安全的情况下提升程序的运行效率

进程池:提前创建好固定数量的进程 后续反复使用这些进程

线程池:提前创建好固定数量的线程 后续反复使用这些线程

"""
如果任务超出了池子里面的最大进程或线程数 则原地等待
进程池和线程池其实降低了程序的运行效率 但是保证了硬件的安全.
"""
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import time
from threading import current_thread

pool = ThreadPoolExecutor(2) # 线程池线程数默认是CPU个数的五倍,也可以自定义
# """创建5个等待工作的线程"""

def task(n):
    time.sleep(2)
    print(n)
    # print(current_thread().name)
    return '啥也不是' # 获取人物的返回值,否则就是None

def run(*args,**kwargs):
    print(args[0].result())  # 通过接收方式接收返回值

for i in range(5):
    pool.submit(task, i).add_done_callback(run)

七.协程

# 协程:单线程下实现并发(切换 保存)
# 进程:资源单位
# 线程:执行单位

#协程就是自己通过代码来检测程序的IO操作并自己处理 让CPU感觉不到IO的存在从而最大幅度的占用CPU

1.协程演练

from gevent import monkey;monkey.patch_all()  # 固定编写 用于检测所有的IO操作
from gevent import spawn
import time


def play(name):
    print('%s play 1' % name)
    time.sleep(5)
    print('%s play 2' % name)


def eat(name):
    print('%s eat 1' % name)
    time.sleep(3)
    print('%s eat 2' % name)


start_time = time.time()
g1 = spawn(play, 'jason')
g2 = spawn(eat, 'jason')
g1.join()  # 等待检测任务执行完毕
g2.join()  # 等待检测任务执行完毕
print('总耗时:', time.time() - start_time)  # 正常串行肯定是8s+
# 5.00609827041626  代码控制切换

八.基于协程实现TCP服务端并发

1.服务端

from gevent import monkey;monkey.patch_all()
from gevent import spawn
import socket

def conmunication(sock):
    while True:
        data = sock.recv(1024) # IO操作
        print(data.decode('utf8'))
        sock.send(data.upper())

def get_server():
    server = socket.socket()
    server.bind(('127.0.0.1',9999))
    server.listen(5)
    while True:
        sock, add = server.accept()  # IO操作
        spawn(conmunication,sock)

g1 = spawn(get_server)
g1.join()

2.客户端

from threading import Thread,current_thread
import socket

def get_client():
    client = socket.socket()
    client.connect(('127.0.0.1',9999))
    count = 0
    while True:
        msg = '%s say hello %s'%(current_thread().name,count)
        count += 1
        client.send(msg.encode('utf8'))
        data = client.recv(1024)
        print(data.decode('utf8'))

for i in range(200):
    t = Thread(target=get_client)
    t.start()

spawn消耗资源,对CPU有一定的负荷
python可以通过开设多进程,在多进程下开设多线程,在多线程使用协程,从而让程序执行的效率达到极致

但是实际业务中很少需要如此之高的效率(一直占着CPU),因为大部分程序都是IO密集型