多线程以及python的多线程

1.1 线程和进程

进程:系统分配资源最小单位,进程指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。
线程:系统调度最小单位,线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程

1.1.1什么是多线程呢?

即就是一个程序中有多个线程在同时执行。
单线程:多个任务只能依次执行。当上一个任务执行结束后,下一个任务开始执行。
多线程:多个任务可以同时执行。

1.2 线程运行原理

分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

1.2.1 抢占式调度详解

大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。比如:现在我们上课一边使用编辑器,一边使用录屏软件,同时还开着画图板,dos窗口等软件。此时,这些程序是在同时运行,”感觉这些软件好像在同一时刻运行着“。
实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。
其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。

1.3 python 多线程

先明确一个问题,Python中的多线程是假的多线程! 为什么这么说,我们先明确一个概念,全局解释器锁(GIL)。
Python代码的执行由Python虚拟机(解释器)来控制。Python在设计之初就考虑要在主循环中,同时只有一个线程在执行,就像单CPU的系统中运行多个进程那样,内存中可以存放多个程序,但任意时刻,只有一个程序在CPU中运行。同样地,虽然Python解释器可以运行多个线程,只有一个线程在解释器中运行。
对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同时只有一个线程在运行。在多线程环境中,Python虚拟机按照以下方式执行。
1.设置GIL。
2.切换到一个线程去执行。
3.运行。
4.把线程设置为睡眠状态。
5.解锁GIL。
6.再次重复以上步骤。

Python多线程相当于单核多线程,多线程有两个好处:CPU并行,IO并行,单核多线程相当于自断一臂。所以,在Python中,可以使用多线程,但不要指望能有效利用多核。如果一定要通过多线程利用多核,那只能通过C扩展来实现,不过这样就失去了Python简单易用的特点。不过,也不用过于担心,Python虽然不能利用多线程实现多核任务,但可以通过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。

1.4 计算密集型和IO密集型

计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。
计算密集型任务由于主要消耗CPU资源,因此,代码运行效率至关重要。Python这样的脚本语言运行效率很低,完全不适合计算密集型任务。对于计算密集型任务,最好用C语言编写。
第二种任务的类型是IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。
IO密集型任务执行期间,99%的时间都花在IO上,花在CPU上的时间很少,因此,用运行速度极快的C语言替换用Python这样运行速度极低的脚本语言,完全无法提升运行效率。对于IO密集型任务,最合适的语言就是开发效率最高(代码量最少)的语言,脚本语言是首选,C语言最差。

1.5 python多线程使用场景

python多线程不适合计算密集型任务,使用多线程可能会考虑全局变量,为了防止线程之间抢占问题会加锁,大大增加了消耗,所以python多线程只适合io密集型的并发上:
单线程:

import time
start_time = time.time()
start_num = 500
n = 1
res = 10000
res_num = 0
while 1:
    res_num += start_num/n
    # print("n:{} res_num={}".format(n, res_num))
    if int(res_num) == res:
        print("n:{} res_num={}".format(n, res_num))
        print("res: {}".format(n))
        break
    else:
        n += 1
    # time.sleep(0.001)
print("spend_time:{}".format(time.time()-start_time))
# n:272400600 res_num=10000.000000810323
# spend_time:63.84967088699341

多线程:
这里慢的原因有两点:1、上下文切换 2、全局变量加锁每次加锁和释放

# 线程中使用全局变量导致效率大打折扣
import threading
import time

start_time = time.time()
threads_num = 10
lock = threading.Lock()
start_num = 500
n = 1
res = 10000
res_num = 0


def demo(a):
    global n, res_num
    time.sleep(1)
    while 1:
        # time.sleep(0.5)
        lock.acquire()
        res_num += start_num / n
        try:
            # print("{}: n:{} res_num={}".format(a, n, res_num))
            if int(res_num) == res:
                print("{}: n:{} res_num={}".format(a, n, res_num))
                break
            else:
                n += 1
        finally:
            lock.release()


threads = []
for i in range(threads_num):
    t1 = threading.Thread(target=demo, args=(i,))
    t1.daemon = True
    threads.append(t1)

for task in threads:
    task.start()
for task in threads:
    task.join()

print("n:{} res_num={}".format(n, res_num))
print("spend_time:{}".format(time.time() - start_time))
# n:272400600 res_num=10000.000017330105
# spend_time:122.03608107566833

多进程:
多进程慢的原因

import time
import multiprocessing

start_num = 500
res = 10000


def demo(a, n, res_num, lock):
    while True:
        with lock:
            res_num.value += start_num / n.value
            # print("{}: n:{} res_num={}".format(a, n.value, res_num.value))
            # time.sleep(0.001)
            if int(res_num.value) >= res:
                print("{}: n:{} res_num={}".format(a, n.value, res_num.value))
                break
            else:
                n.value += 1


if __name__ == '__main__':
    start_time = time.time()
    multiproces_num = 2
    multiproces_list = []
    n = multiprocessing.Value('i', 1)
    res_num = multiprocessing.Value('d', 0)
    lock = multiprocessing.Lock()
    for i in range(multiproces_num):
        p1 = multiprocessing.Process(target=demo, args=(i, n, res_num, lock,))
        multiproces_list.append(p1)
    for task in multiproces_list:
        task.start()
    for task in multiproces_list:
        task.join()
    print("multiproces_num:{}  spend_time:{}".format(multiproces_num, time.time() - start_time))
# 时间太长了超过了5分钟不等了

总结:对于运算简单的函数/方法,使用单线程即可最高效率发挥性能优势,使用多线程/多进程由于全局变量/共享变量需要加锁防止抢占,徒增了锁的开销,反而会大大降低运行效率增加性能开销

多线程适用的场景:
单线程:只能先:吃饭(10s),再:看电影(10s),最后:听音乐(5s)

import time
start_time = time.time()
print("start eat...")
time.sleep(10)
print("end eat")
print("start movie...")
time.sleep(10)
print("end movie")
print("start music...")
time.sleep(5)
print("end movie")
print("spend time:{}".format(time.time()-start_time))
# spend time:25.008626222610474

多线程:吃饭(10s)、看电影(10s)、听音乐(5s)同时进行

import threading
import time

start_time = time.time()


def eat():
    print("start eat...")
    time.sleep(10)
    print("end eat")


def movie():
    print("start movie...")
    time.sleep(10)
    print("end movie")


def music():
    print("start music...")
    time.sleep(10)
    print("end music")


threads = []
t1 = threading.Thread(target=eat)
t1.daemon = True
threads.append(t1)
t1 = threading.Thread(target=movie)
t1.daemon = True
threads.append(t1)
t1 = threading.Thread(target=music)
t1.daemon = True
threads.append(t1)

for task in threads:
    task.start()
for task in threads:
    task.join()

print("spend_time:{}".format(time.time() - start_time))
# spend_time:10.004291772842407

另外,切勿使用python进行cpu密集型计算,性能太差,附上上述例子go的简单实现
部分原因是:python中所有皆为对象,每次计算出来的值都要重新赋值,内存地址每次都会发生改变

package main

import (
	"fmt"
	"time"
)

var timeUnixNano = time.Now().UnixNano()//单位纳秒
var start_num float64 = 500
var n float64 = 1
var res float64 = 10000
var res_num float64 = 0
var add_num float64

func main(){
	for true{
		add_num = start_num/n
		res_num = res_num + add_num
		//fmt.Println("n=",n)
		//fmt.Println("res_num=",res_num)
		if res_num >= res{
			fmt.Println("n=",n)
			fmt.Println("res_num=",res_num)
			fmt.Println("time spend:",(time.Now().UnixNano() - timeUnixNano)/ 1e6)
			break
		}else{
			n ++
		}

	}
}

// n= 2.724006e+08
// res_num= 10000.000000810323
// time spend: 612  毫秒
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值