c语言多线程运行在多核上,python下多核,单核CPU对于并行,并发执行效率的对比-Go语言中文社区...

**

**

这篇博客主要内容为python 中多线程以及多进程的效率对比,以及记录自己在做这个实验中遇到的一些问题以及心得

背景引入:

CPU制造商为了追求CPU效率放弃了在CPU频率上的追求(CPU频率即CPU单位时间内可以完成任务的多少),反而开始把方向转向了多核CPU上。那么如何在多核CPU上充分发挥出多核的优势就成了一个问题。

首先是简单介绍下多线程与多进程:

线程:

是程序执行流的最小单元,是系统独立调度和分配CPU(独立运行)的基本单位。线程(有时被称为轻量级进程)跟进程有些相似,不同的是,所有的线程运行在同一个进程中, 共享相同的运行环境。它们可以想像成是在主进程或“主线程”中并行运行的“迷你进程”。

进程:

是资源分配的基本单位。一个进程包括多个线程。进程(有时被称为重量级进程)是程序的一次 执行。每个进程都有自己的地址空间,内存,数据栈以及其它记录其运行轨迹的辅助数据。操作系 统管理在其上运行的所有进程,并为这些进程公平地分配时间。进程也可以通过 fork 和 spawn 操作 来完成其它的任务。不过各个进程有自己的内存空间,数据栈等,所以只能使用进程间通讯(IPC), 而不能直接共享信息。

说简单点进程就好比QQ,浏览器这些应用程序,而线程就像QQ里和不同的人的聊天窗口或者浏览器中播放的音乐,显示的网页。

区别:

1.线程与资源分配无关,它属于某一个进程,并与进程内的其他线程一起共享进程的资源。

2.每个进程都有自己一套独立的资源(数据),供其内的所有线程共享。

3.不论是大小,开销线程要更“轻量级”

4.一个进程内的线程通信比进程之间的通信更快速,有效。(因为共享变量)

测试机:

物理机

虚拟机操作系统

macOS

Ubuntu16.04

CPU

双核

单核

内存

8G

2G

python在管理多线程使用了GIL

pythonGIL解释:

GIL是实现python解释器时引入的一个概念,像C语言一样,python 的解释器也有很多,常见的有CPython,Psyco,PyPy。 而GIL是实现CPyhon时引入的。

In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)

GIL的出现是有历史原因的,对于并行和并发这样的多任务,就是为了提高CPU的使用效率,然而需要注意的是,一个CPU一个时间只能实现一个任务,也就是说一个CPU永远不可能并行的,但是可以借助CPU轮训制度频繁切换任务,完成多任务。

python在刚创立的时候只考虑到了单核CPU,那么考虑到多线程的数据完整性以及状态同步就需要加一把大锁,所以呢,GIL应运而生。

GIL说简单点就是一把大锁,一把有很大权力的锁,一把可以控制CPU的锁,它可以保证一个时间内只能有一个CPU在工作,确保了多线程数据完整性和状态同步,看到这个地方,大家就有疑惑了,那还要多核CPU干什么,都成单核的了。这也确实是GIL局限性的地方。从上面的英文解释中可以看出GIL可

+以保证线程的安全,刚看到这个概念我有点疑惑,难道操作系统自身的线程调度机制不可以吗,通过查找操作系统的资料我发现,操作系统还真的不可以,下面介绍几种调度算法;

1、先到先服务调度算法(FCFS)

根据就绪队列的到达时间来服务,此时就绪队列是一个FIFO队列,先到先服务,后到的线程不能抢占前面正在服务的线程。这种算法的优点是实现简单,缺点也很明显,就是CPU进程区间变化很大时,平均等待时间会变化很大。

2、最短作业优先调度(SJF)

顾名思义,就是CPU进程区间最短的先执行,如果两个进程区间具有同样的长度,那么按照FCFS来调度。

SJF可以是抢占的,也可以是不抢占的。它的平均等待时间优于FCFS。

3、优先级调度

其实上面的SJF算法就是一种特殊的优先级调度,只不过这里的优先级定义更加广泛一些,SJF算法的优先级是按照CPU进程区间长短来定义的,这里的优先级可以是其他的一些定义。

优先级调度可以是抢占的,也可以是非抢占的。

优先级调度的一个主要问题是无穷阻塞(也称为饥饿),如果一个线程的优先级很低,可能需要等待很长的时间才能到这个线程执行,甚至永远不执行,一种解决方法是老化(随着时间的增长,增加线程的优先级)

4、轮转法调度(RR)

轮转法调度专门是为分时系统设计的。它类似于FCFS,但是增加了抢占为了切换线程。定义一个较小的时间单元,称为时间片,通常为10-100ms。为了实现RR算法,将就绪队列保存为FIFO队列,新进程增加到就绪队列队尾,CPU调度程序从就绪队列选择第一个进程,设置定时器在一个时间片之后再中断,再分派这个进程。

如果该进程的CPU区间小于时间片,进程本身就会释放CPU,调度程序继续处理下一个进程,如果当前进程的CPU区间比时间片长,定时器会产生CPU中断,实行上下文切换,然后将此进程放到就绪队列队尾,继续调度就绪队列第一个进程。

可以看出计算机并不知道代码的具体含义,所以如果代码只是使用数据就没有问题,如果代码要改变数据可能会导致再多线程时数据不同步状态不统一,所以GIL这把锁就可以保证线程的安全。正如上文所说,GIL也确实是一个缺陷无法充分体现出多核的优势,那么我们应该避免这个问题呢:

1.用其他解析器

之前也提到了既然GIL只是CPython的产物,那么其他解析器是不是更好呢?没错,像JPython这样的解析器由于实现语言的特性,他们不需要GIL的帮助。然而由于用了Java/C#用于解析器实现,他们也失去了利用社区众多C语言模块有用特性的机会。所以这些解析器也因此一直都比较小众。所以这个方法并不推荐

2.使用多进程代替多线程

用multiprocessing替代Thread

multiprocessing库的出现很大程度上是为了弥补thread库因为GIL而低效的缺陷。它完整的复制了一套thread所提供的接口方便迁移。唯一的不同就是它使用了多进程而不是多线程。每个进程有自己的独立的GIL,因此也不会出现进程之间的GIL争抢。当然multiprocessing也不是万能良药。它的引入会增加程序实现时线程间数据通讯和同步的困难。multiprocessing由于进程之间无法看到对方的数据,只能通过在主线程申明一个Queue,put再get或者用share memory的方法。这个额外的实现成本使得本来就非常痛苦的多线程程序编码,变得更加痛苦了。但是这仍然是我觉得对于初学者来说最友好的方法了。

代码实现

python中关于线程的使用涉及到threading模块,进程使用涉及multiprocessing模块

都只是使用最基本的start,len方法,所以这里不多赘述。

直接上代码:

需要的第三方库

import requests

import time

import threading

from multiprocessing import Process

首先是线性执行CPU密集型函数,IO密集型函数

# 定义CPU密集型函数

def count(x, y):

c = 0

while c < 500000:

c += 1

x += x

y += y

#定义IO密集型函数

def write():

f = open("test.txt", "w")

for x in range(500000):

f.write("testwriten")

f.close()

def read():

f = open("test.txt", "r")

lines = f.readlines()

f.close()

#定义网络请求函数

head_request = {

'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36'

}

url = "http://www.tieba.com"

def http_request():

try:

res = requests.get(url, headers=head_request)

html = res.text

return {"context": html}

except Exception as e:

return {"error": e}

# CPU密集操作

t = time.time()

for x in range(10):

count(1, 1)

print("cpu函数运行时间:", time.time() - t)

# IO密集操作

t = time.time()

for x in range(10):

write()

read()

print("IO函数运行时间:", time.time() - t)

b65e263000b8fd66c5bcfc0d260438fc.png

6c884da619cca2e3c4c462b136cd367d.png

接下来是多线程并发模拟CPU密集型函数`

# 定义CPU密集型函数

def count(x, y):

c = 0

while c < 500000:

c += 1

x += x

y += y

counts = []

t = time.time()

for x in range(10):

thread = threading.Thread(target=count, args=(1,1))

counts.append(thread)

thread.start()

e = counts.__len__() #这里使用了一个魔术方法,获取counts的长度

while True:

for i in counts:

if not i.is_alive():

e -= 1

if e <= 0:

break # 当所有线程执行完毕后退出

print(time.time() - t)

4ddba9cce3a3c36dd6d1c1617ef5c7b0.png

a3610654d764aa57c28fc30eb52578a1.png

多进程模拟CPU密集型函数

# 定义CPU密集型函数

def count(x, y):

c = 0

while c < 500000:

c += 1

x += x

y += y

counts = []

t = time.time()

for x in range(10):

process = Process(target=count, args=(1,1))

counts.append(process)

process.start()

e = counts.__len__()

while True:

for i in counts:

if not i.is_alive():

e -= 1

if e <= 0:

break

print(time.time() - t)

636209bb481b5f17aac5f29052bcb162.png

5f650acbb836402de405dbde72e2a2e6.png

物理机

虚拟机线性执行CPU密集型函数

65.29

120

线性执行IO密集型函数

1.76

1.6

多线程并发模拟CPU密集型函数

65.92

132.58

多进程并行模拟CPU密集型函数

38.87

127.9

通过上面的表格进行数据对比,明显发现在多核的情况下,python的多进程要比多线程快得多。那么是为什么呢,这里就要说到python的多线程管理机制,在python3.x中,GIL使用计时器(执行时间达到阈值后,当前线程释放GIL)对线程进行管理,这样对CPU密集型程序更加友好,但依然没有解决GIL导致的同一时间只能执行一个线程的问题,所以效率依然不尽如人意。而且在多核的情况下可能在进程调度轮训的过程中还会产生CPU的竞争会产生更加坏的效果。

#GIL即全局解释器锁

然而对于IO密集型函数或者网络请求函数,多线程就是友好地,因为可以利用函数的挂起空闲时间进行线程的转换,充分利用到了多核CPU的优势。

在单核的情况下,模拟的多进程并行的效率也是优于多线程并发的。

Liunx多线程pthread初探:https://blog.csdn.net/xuanandting/article/details/78842795

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值