python并发编程——多线程

编程的乐趣在于让程序越来越快,这里将给大家介绍一个种加快程序运行的的编程方式——多线程

1 著名的全局解释锁(GIL)

说起python并发编程,就不得不说著名的全局解释锁(GIL)了。有兴趣的同学可以我查找一下相关的资料了解一下GIL,在这里大家只要知道一点,因为GIL的存在,对于任何Python程序,不管有多少的处理器,任何时候都总是只有一个线程在执行。

下面先看一个例子:

#!/usr/bin/env python

# -*- coding: utf-8 -*-

"""

Created on Mon Jun 5 16:12:14 2017


@author: 80002419

"""

import threading

import time


defcost(fun):##定义一个装饰器,用来计算函数运行的时间

defwrapper(*args,**kargs):

before_tm = time.time()

fun(*args,**kargs);

after_tm = time.time()

fun.__doc__

print("{0} cost:{1}".format(fun.__name__,after_tm-before_tm))

return wrapper


deffibs1(n):

if (n ==1):

return0

elif(n ==2):

return1

else:

return fibs1(n-2)+fibs1(n-1)


deffibs2(n):

list = []

if (n ==1):

list = [0]

return list[-1]

elif(n ==2):

list = [0,1]

return list[-1]

else:

list = [0,1]

for iin range(2,n):

list.append(list[i-1]+list[i-2])

return list[-1]


@cost

defnothread():

fibs1(35)

fibs1(35)



#@cost

#def nothread1():

# print(fibs2(40))

# print(fibs2(40))


@cost

#使用多线程的程序

definthread():

threads = []

for iin range(2):

t = threading.Thread(target = fibs1,args =(35,))

t.start()

threads.append(t)

for tin threads:

t.join()


@cost

definthread1():

for iin range(2):

t = threading.Thread(target=fibs1, args=(35,))

t.start()

main_thread = threading.currentThread()

for tin threading.enumerate():

if tis main_thread:

continue

t.join()


nothread()

inthread()

打印结果:

nothread cost:5.41788887978

inthread cost:14.6728241444


上面的例子对比了一个cpu密集型程序分别使用多线程和不使用多线程的情况,我们可以得到结论:对于cpu密集型任务,使用多线程反而会减慢程序的运行。

这是为什么呢?

 因为GIL在任何时候,仅仅允许一个单一的线程能够获取Python对象或者C API。每100个字节的Python指令解释器将重新获取锁,这(潜在的)阻塞了I/O操作

同时GIL是必须的,这是Python设计的问题:Python解释器是非线程安全的。这意味着当从线程内尝试安全的访问Python对象的时候将有一个全局的强制锁。当然python

多线程也有可以加快程序运行的时候:当我们开发和网络通信或者数据输入/输出相关的程序,比如网络爬虫、文本处理等等。这时候由于网络情况和I/O的性能的限制,Python解释器会等待读写数据的函数调用返回,这个时候就可以利用多线程库提高并发效率了。这类任务我们称为IO(密集型任务)


2  Semaphore类 ——python对象访问量的控制

在多线程编程中,为了防止不同的线程同时对一个公用的资源(比如全部变量)进行修改,需要进行同时访问的数量(通常是1)。信号量同步基于内部计数器,每调用一次acquire(),计数器减1;每调用一次release(),计数器加1.当计数器为0时,acquire()调用被阻塞。

下面来看一个例子:

#!/usr/bin/env python

# -*- coding: utf-8 -*-

"""

Created on Mon Jun  5 16:12:14 2017


@author: 80002419

"""

from threadingimport Thread, Semaphore

import time,threading


sema = Semaphore(3)# 定义一个信号量为3的Semaphone对象,


#定义一个测试函数作为,被访问的对象

deftest(tid):

with sema:

print("信号量:{0}".format(tid))

time.sleep(0.5)

print('release!')


thds = []

for iin range(6):

t = Thread(target = test,args=(i,))

t.start()

thds.append(t)


for tin thds:

t.join()

打印结果:

信号量:2

信号量:1

信号量:0

release!

信号量:0

release!

信号量:0

release!

信号量:0

release!

release!

release!


结果分析 :

当信号量sema信号为0时,此时不再不允许进程对test 再进行访问了,release 之后才能再继续生成亲的进程对其访问,Semaphore类的作用就是限制公共资源的访问量


3 锁——lock 与 Rlock

先看一段代码:

#!/usr/bin/env python

# -*- coding: utf-8 -*-

"""

Created on Mon Jun  5 16:12:14 2017


@author: 80002419

"""

from threadingimport Thread, Lock

import time,threading


value =0;


defchangeValue():

global value

new = value +1

time.sleep(0.001)#让其它进程可以切换进来

value = new


thds = []

for _in range(100):

t = Thread(target = changeValue)

t.start()

thds.append(t)


for tin thds:

t.join()


print(value)

打印结果:

18

结果分析: 多个线程同时访问 value

变量,经过cpu存储后,再写回内存,如果进程A,进程B,在访问value时 值为0 ,进程A经过函数处理后,value = 1

,再写回内存,value =1 ,进程B 执行完成后,会刷新value 但是写回的value 还是1,

所以并不能保护执行了100次changeValue后,结果为100


那么我们怎么能保证执行的结果就是100呢:再看一段代码:

#!/usr/bin/env python

# -*- coding: utf-8 -*-

"""

Created on Mon Jun  5 16:12:14 2017


@author: 80002419

"""

from threadingimport Thread, Lock

import time,threading


value =0


lock = Lock()


defchangeValue():

global value

with lock:

new = value +1

time.sleep(0.001)#让其它进程可以切换进来

value = new


thds = []


for _in range(100):

t = Thread(target = changeValue)

t.start()

thds.append(t)

for tin thds:

t.join()

print(value)

打印结果:

100

Lock也可以叫做互斥锁,其实相当于信号量为1,上面例子lock 也就可以用 sema = Semaphore()代替

本文的文字及图片来源于网络,仅供学习、交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理

想要获取更多Python学习资料可以加

QQ:2955637827私聊

或加Q群630390733

大家一起来学习讨论吧!

你可能感兴趣的:(python并发编程——多线程)