Python的多线程与多进程

这个问题当时是在一个群里看到的,Python到底有没有多线程。群里甚至还有两个人因为这个玩意吵起来,要现实干仗了。233333333
然后我就把那个乌烟瘴气的群退了。由此学习记录一下python中的多线程和多进程。

1.Python中的GIL锁

首先,要谈多进程,就要说一下这个锁的问题。
GIL锁全名是,全局解释器锁(Global Interpreter Lock,缩写GIL),是计算机程序设计语言解释器用于同步线程的一种机制,它使得任何时刻仅有一个线程在执行,即便是在多核心处理器上,使用GIL的解释器也只允许同一时间执行一个线程。常见的使用GIL的解释器有CPython和Ruby MRI。
所以,解释的通俗一点,就是即时你开了多线程,但是如果执行CPU密集型的任务去请求CPU运算时,还是要获得这个GIL锁才能请求到CPU,但是这个GIL锁,只有一把,因此每次都是只有一个线程能请求到这个锁,然后使用CPU执行相应任务,这个锁的获取,是通过时间片轮转算法来获得的,所以,说到底,这玩意还是一个一个的执行,和单线程裸奔无异,但是前提是请求CPU执行任务,即CPU密集型任务。看一下这个经典的图。
在这里插入图片描述
我可能说的不够好,知乎中有一篇文章也有相关介绍,下面是链接。
https://zhuanlan.zhihu.com/p/97218985

2.代码的测试

单线程裸奔

import time
# 单线程裸奔
def start():
    i = 0
    for i in range(10000000):
        i += 1
    return

def main():
    s = time.time()
    for i in range(10):
        start()
    print(time.time() - s)

if __name__ == '__main__':
    main()

在这里插入图片描述
多线程执行

import time
import threading

# 多线程执行

def start():
    i = 0
    for i in range(10000000):
        i += 1
    return

def main():
    s = time.time()
    ts = {}
    for i in range(10):
        t = threading.Thread(target=start)
        t.start()
        ts[i] = t       # 线程存储在字典中

    for i in range(10):
        ts[i].join()    # 执行完毕后阻塞

    print(time.time() - s)

if __name__ == '__main__':
    main()


在这里插入图片描述
效果并没有很好,造成这种情况的原因就是GIL锁,由于存在这个GIL锁,所以在CPU密集型任务上,消耗的时间反而会更多IO密集型则可以达到较好的效果,因为不会用到CPU做很多运算,所以就不会收到GIL锁的干扰
要是想更好的解决,就是用多进程,而不是多线程,每个进程都有自己独立的GIL,不会出现进程之间GIL的争抢
多进程的创建和销毁开销会很大,成本会更高,而且进程之间无法看到对方的数据,需要使用栈或者队列进行获取,从而提升编程复杂度

3.补充有关线程的几个小点

join()方法

import threading
import time

def start():
    time.sleep(5)
    print(threading.current_thread().name)  # 线程名
    print(threading.current_thread().isAlive()) # 线程是否存活
    print(threading.current_thread().ident)# 线程ID

print('start')
# 注意,这里的参数是方法名,start,而不是start(),加了()就会执行函数,而不是传参,
t =threading.Thread(target=start,name='number one')
t.start()
print('stop')

在这里插入图片描述
出现这种情况的原因是,定义的线程为非守护线程,线程的执行并不会随着主线程结束而结束。
使用join()方法

import threading
import time

def start():
    time.sleep(5)
    print(threading.current_thread().name)  # 线程名
    print(threading.current_thread().isAlive()) # 线程是否存活
    print(threading.current_thread().ident)# 线程ID

print('start')
# 注意,这里的参数是方法名,start,而不是start(),加了()就会执行函数,而不是传参,
t =threading.Thread(target=start,name='number one')
t.start()
t.join()    # join()方法,作用是阻塞,等待子线程结束,join方法有一个参数是timeout,即如果主线程等待timeout,
            # 子线程还没有结束,则主线程强制结束子线程。
print('stop')

在这里插入图片描述
守护线程

# 守护线程,会伴随主线程一起结束
# setDaemon设置为True即可,但是如果守护线程使用了 join方法,还是会等线程执行完毕,再执行主线程
import threading
import time

def start():
    time.sleep(5)
    print(threading.current_thread().name)  # 线程名
    print(threading.current_thread().isAlive()) # 线程是否存活
    print(threading.current_thread().ident)# 线程ID

print('start')
# 注意,这里的参数是方法名,start,而不是start(),加了()就会执行函数,而不是传参,
t =threading.Thread(target=start,name='number one')
t.setDaemon(True)       # 设置为守护线程,主线程结束,守护线程无论执行完毕与否,都会结束
t.start()
print('stop')

在这里插入图片描述
有关线程对共享资源的读取问题

import threading
number = 0
def addNumber():
    global number
    for i in range(1000000):
        number += 1

def downNumber():
    global number
    for i in range(1000000):
        number -= 1

print("start")
t = threading.Thread(target=addNumber)
t2 = threading.Thread(target=downNumber)

t.start()
t2.start()
t.join()
t2.join()
print(number)
print("stop")

在这里插入图片描述
按照常理来说,最后这个number应该是等于0,但是却出现了这种情况,原因这里解释下,这个原因的解释也是来源于上面知乎中的文章,那篇文章中的解释比较清楚。

上面的程序对n做了同样数量的加法和减法,那么n理论上是0。但运行程序,打印n,发现它不是0。问题出在哪里呢,问题在于python的每行代码不是原子化的操作。比如n = n+1这步,不是一次性执行的。如果去查看python编译后的字节码执行过程,可以看到如下结果。

19 LOAD_GLOBAL              1 (n)
22 LOAD_CONST               3 (1)
25 BINARY_ADD          
26 STORE_GLOBAL             1 (n)

从过程可以看出,n = n +1 操作分成了四步完成。因此,n = n+1不是一个原子化操作。
1.加载全局变量n,2.加载常数1,3.进行二进制加法运算,4.将运算结果存入变量n。
根据前面的线程释放GIL锁原则,线程a执行这四步的过程中,有可能会让出GIL。如果这样,n=n+1的运算过程就被打乱了。最后的结果中,得到一个非零的n也就不足为奇。

解决方法:加锁,在对共享资源访问时进行加锁。

# 解决方法,加锁
import threading
lock = threading.Lock()
number = 0
def addNumber():
    global number
    for i in range(1000000):
        lock.acquire()
        number += 1
        lock.release()

def downNumber():
    global number
    for i in range(1000000):
        lock.acquire()
        number -= 1
        lock.release()

print("start")
t = threading.Thread(target=addNumber)
t2 = threading.Thread(target=downNumber)

t.start()
t2.start()
t.join()
t2.join()
print(number)
print("stop")

在这里插入图片描述
一种控制锁粒度的递归锁

# 递归锁,为了将锁的粒度控制的更小,更精准,需要使用递归锁
# 缺点:很慢
import time
import threading

class Test:
    rlock = threading.RLock()
    def __init__(self):
        self.number = 0

    def add(self):
        with Test.rlock:    # 这里加了一把锁,执行execute方法,执行后释放
            self.execute(1)

    def down(self):
        with Test.rlock:
            self.execute(-1)

    def execute(self,n):
        # with关键字的使用与打开文件的功能类似,实现自开合效果,
        # 会自动的加锁和释放
        with Test.rlock:    # 这里又加了一把锁,等到执行完加法之后释放
            self.number += n


def add(test):
    for i in range(10000000):
        test.add()

def down(test):
    for i in range(10000000):
        test.down()

if __name__ == '__main__':
    test = Test()
    # args传递方法执行所需要的参数
    t1 = threading.Thread(target=add,args=(test,))
    t2 = threading.Thread(target=down,args=(test,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(test.number)

这个会运行好久,因为递归锁很慢。。
在这里插入图片描述
线程池

# 线程池的包
import time
import threadpool

# 执行较为耗时的函数,需要开启多线程
def get_html(url):
    time.sleep(3)
    print(url)

# 使用多线程执行telnet函数
urls = [i for i in range(10)]
# 建立线程池
pool = threadpool.ThreadPool(10)
# 提交任务给线程池
requests = threadpool.makeRequests(get_html,urls)
# 开始执行任务
for req in requests:
    pool.putRequest(req)
pool.wait()
4.多进程

多进程则与多线程不同了,多进程在进行CPU密集型计算时,并不会出现GIL锁问题。多个进程并发执行,而且在进程中可以开启线程。
但缺点就是:进程是资源分配的基本单位,因此多进程开启会比较慢。

import multiprocessing
import time
"""
    多进程
"""

def start(i):
    time.sleep(1)
    print(i)
    print(multiprocessing.current_process().name)
    print(multiprocessing.current_process().pid)
    print(multiprocessing.current_process().is_alive())

if __name__ == '__main__':
    print("start")
    p = multiprocessing.Process(target=start,args=(1,),name="Mul process")
    p.start()
    # p.join()
    print("stop")

在这里插入图片描述
多进程中的操作,与多线程类似。
进程通信

# 进程通信
# Python多进程之间默认是无法通信的,因为是并发执行的,所以需要借助其他数据结构
# 这里借助队列,一个往队列中写,一个从队列中读,实现消息队列
from multiprocessing import Process,Queue

def write(q):
    print("Process to write : %s" % Process.pid)
    for i in range(100):
        print("Put %d to queue..." % i)
        q.put(i)

def read(q):
    print("Process to read : %s" % Process.pid)
    while True:
        value = q.get()
        print("Get %d from queue." % value)

if __name__ == '__main__':
    # 父进程创建Queue,并传递给各个子进程
    q = Queue()
    pw = Process(target=write,args=(q,))
    pr = Process(target=read,args=(q,))
    pw.start()
    pr.start()


进程池
使用multiprocessing模块实现进程池操作

# 进程池

import multiprocessing

def function_square(data):
    result = data * data
    return result

if __name__ == '__main__':
    inputs = list(range(100))
    pool = multiprocessing.Pool(processes=4) # 大小为4
    # map把任务交给进程池处理
    pool_outputs = pool.map(function_square,inputs)
    # apply每次提交一个任务
    # pool_outputs = pool.map(function_square,args=(10,))
    pool.close()
    pool.join()
    print("Pool    :",pool_outputs)

5.concurrent.futures模块中,对三种执行方式的对比
# 单线程裸奔执行CPU密集型任务
import concurrent.futures
import time
number_list = [1,2,3,4,5,6,7,8,9,10]

def evaluate_item(x):
    result = count(x)
    return result

def count(number):
    for i in range(0,10000000):
        i+=1
    return number

if __name__ == '__main__':
    # 单线程裸奔
    s1 = time.time()
    for item in number_list:
        print(evaluate_item(item))
    print("单线程裸奔:",time.time() - s1)

    # 多线程,线程池执行
    s2 = time.time()
    with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
        futures = [executor.submit(evaluate_item,item) for item in number_list]
        for future in concurrent.futures.as_completed(futures):
            print(future.result())
    print("多线程执行:",time.time() - s2)

    # 多进程,进程池执行
    s3 = time.time()
    with concurrent.futures.ProcessPoolExecutor(max_workers=5) as executor:
        futures = [executor.submit(evaluate_item, item) for item in number_list]
        for future in concurrent.futures.as_completed(futures):
            print(future.result())
    print("多进程执行:", time.time() - s3)

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值