线程的应用

1 > GIL与普通互斥锁的区别

1.1 > 先验证GIL的存在

   例如我们先创建100 个线程,让每个线程都去修改主线程中的数据。由于GIL锁的存在,子线程会依此对主线程中的数据进行修改,操作如下:


from threading import Thread
money = 100

def task():
	global money
	money -= 1
for i in range(100):				# 创建100个线程
	t = Thread(target=task)
	t.start()
print(money)						# 0

在这里插入图片描述

1.2 > 验证不同数据加不同锁

from threading import Thread, Lock
import time

money = 100
mutex = Lock()

def task():
	global money
	mutex.acquire()						# 子线程必须枪锁之后再执行子线程代码
	tmp = money							
	time.sleep(0.1)
	money = tmp - 1
	mutex.release()						# 子线程在执行完代码后需要放锁,之后的线程才可以继续枪锁修改
	'''
	枪锁方锁也有简便的写法(with 上下文管理)
	with mutex:
		pass
	'''
t_list = []
for i in rang(100):
	t = Thread(target=task)
	t.start()
	t_list.append(t)
for t in t_list:
	t.join()
	
print(money)
# 为了确保结构正确 应该等待所有的线程运行完毕再打印money

在这里插入图片描述

2 > 验证多线程作用

  有两种情况,单个cpu和多个cpu

2.1 > 单个cpu

	
	多个IO密集型任务
	多进程: 浪费资源 无法利用多个CPU
	多线程: 节省资源 切换+保存状态

	多个计算密集型任务
	多进程:耗时更长 创建进程的消耗+切换消耗
	多线程:耗时较短 切换消耗

2.2 > 多个cpu

	
	多个IO密集型任务
	多进程:浪费资源 多个CPU无用武之地
	多线程:节省资源 切换+保存状态

	多个计算密集型任务
	多进程:利用多核 速度更快
	多线程:速度较慢
	

  结论就是:多进程和多线程都有具体的应用场景 尤其是多线程并不是没有用。

2.3 > 代码验证

									''' 模拟创建多进程处理密集型计算任务 '''
from multiprocessing import Process
import time


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


if __name__ == '__main__':
    start_time = time.time()
    p_list = []
    for i in range(8):
        p = Process(target=work)
        p.start()
        p_list.append(p)

    for p in p_list:
        p.join()
    print('总耗时:%s' % (time.time() - start_time))

在这里插入图片描述

									''' 模拟创建多线程处理密集型计算任务 '''

from threading import Thread
import time


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


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

在这里插入图片描述

   由此可以得出,当处理计算密集型任务时。多进程耗时时间:5.4863…, 多线程耗时时间:18.315… 所以可以知道时多进程更好。

									''' 模拟创建多线程处理IO密集型任务 '''
from threading import Thread
import time


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


if __name__ == '__main__':
    start_time = time.time()
    t_list = []
    for i in range(100):
        t = Thread(target=work)
        t.start()

    for t in t_list:
        t.join()

    print('多线程总耗时:%s' %(time.time() - start_time))

在这里插入图片描述

									''' 模拟创建多进程处理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(100):
        p = Process(target=work)
        p.start()
    for p in p_list:
        p.join()

    print('多线程总耗时:%s' %(time.time() - start_time))

在这里插入图片描述
   由此可以得出,当处理计算密集型任务时。多进程耗时时间:2.4953…, 多线程耗时时间:0.0149… 所以可以知道此时多4线程更好。

3 > 死锁现象

   锁的知识我们都知道了,但是就算掌握了如何枪锁,如何放锁,也会产生死锁的现象。代码如下:


from threading import Thread, Lock
import time

# 产生两把锁A,B
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(20):
    t = MyThread()
    t.start()
    

在这里插入图片描述
   所以说对于互斥锁我们仅仅需要理解它是如何执行的,在工作中让我们自己写这些的情况都是很少很少的,我们都是会去用一些别人别人写好的不会死锁模块。

4 > 信号量(了解)

  信号量在不同的知识体系中,展示出来的功能是不一样的。在并发编程中信号量是多把互斥锁,在django框架中信号量的意思是达到某个条件自动触发特定功能。我们可以将信号量在并发编程中形象的比喻成公共厕所(有多个坑位),将自定义互斥锁比喻成是单个厕所(一个坑位)。操作代码如下:


from threading import Thread, Semaphore
import time
import random

sp = Semaphore(5)  # 创建五个带有门坑位的公共厕所


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


for i in range(1, 31):
    t = Thread(target=task, args=('伞兵%s 号' % i,))
    t.start()

在这里插入图片描述

5 > event事件(了解)

  之前我们学过用join方法让主进程/主线程,控制子进程/子线程,然而event事件是可以让子线程的运行可以由其他子线程决定。代码如下:


from threading import Thread, Event
import time

event = Event()  # 类似于造了一个红绿灯


def light():
    print('红灯亮着的 所有人都不能动')
    time.sleep(10)
    print('3 2 1 GO !!!')
    event.set()


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


t = Thread(target=light)		# 创建主线程
t.start()

for i in range(3):
    t = Thread(target=car, args=('熊猫 PRO%s' % i,))		# 创建子线程
    t.start()

在这里插入图片描述

6 > 进程池与线程池(重点)

   池: 保证计算机硬件安全的情况下提升程序的运行效率
   进程池的意思就是: 提前创建好固定数量的进程 后续反复使用这些进程(合同工)
   线程池的意思就是: 提前创建好固定数量的线程 后续反复使用这些线程(合同工)
   如果任务超出了池子里面的最大进程或线程数 则原地等待
   进程池和线程池其实降低了程序的运行效率 但是保证了硬件的安全!!!
   代码演示如下:

						'''	线程池演示代码 '''
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
import time

pool = ThreadPoolExecutor(5)  # 线程池线程数默认是cpu个数的五倍,也可以自定义
'''
上面的代码执行之后就会立刻创建5个等待工作的线程
'''
def task(n):
    time.sleep(3)
    print(n)
    return '任务的执行结果 :%s' % (n ** 2)


def func(*args, **kwargs):
    print(args[0].result())


for i in range(10):
    pool.submit(task, i).add_done_callback(func)  # 朝线程池中提交任务(异步)
	'''
	异步回调机制
	add_done_callback只要任务有结果了 就会自动调用括号内的函数处理
	'''

在这里插入图片描述

						'''	进程池演示代码 '''
from concurrent.futures import ProcessPoolExecutor
import time
import os

pool = ProcessPoolExecutor(4)


def task(m):
    time.sleep(1)
    print(m)
    print(os.getpid())
    return '程序运行结果%s' % (m ** 2)


def function(*args, **kwargs):
    print(args[0].result())


if __name__ == '__main__':
    for t in range(5):
        pool.submit(task, t).add_done_callback(function)
	'''
	异步回调机制
	add_done_callback只要任务有结果了 就会自动调用括号内的函数处理
	'''

在这里插入图片描述

7 > 协程

   协程其实是程序员自己相出来的一个名词,对于操作系统而言只认识进程和线程,协程其实就是自己通过代码来检测程序的IO操作并自己处理,让CPU感觉不到IO的存在从而大幅度的占用CPU。就类似于一个人同时干服务和接待客人的工作,在接待和服务之间来回切换。代码演示如下:


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(4)
	print('%s eat 2' % name)

start_time = time.time()
g1 = spawn(play, 'bob')
g2 = spawn(eat, 'bob')
g1.join()				# 等待检测任务执行完毕
g2.join()				# 等待检测任务执行完毕
print('总耗时: ', time.time() - start_time)

在这里插入图片描述

8 > 基于协程实现TCP服务端并发

   python可以通过开设多进程 在多进程下开设多线程 在多线程使用协程,从而让程序执行的效率达到极致!!!
   但是实际业务中很少需要如此之高的效率(一直占着CPU不放),因为大部分程序都是IO密集型的,所以协程我们知道它的存在即可 几乎不会真正去自己编写。

			'''服务端'''
from gevent import monkey;

monkey.patch_all()
from gevent import spawn
import socket


def communication(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', 8080))
    server.listen(5)
    while True:
        sock, addr = server.accept()
        spawn(communication, sock)


g1 = spawn(get_server)
g1.join()

							'''客户端'''
from threading import Thread, current_thread
import socket

def get_client():
    client = socket.socket()
    client.connect(('127.0.0.1', 8080))
    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(20):
    t = Thread(target=get_client)
    t.start()

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值