Py学习  »  Python

python threading模块进行多线程编程

生信修炼手册 • 3 年前 • 485 次点击  
欢迎关注”生信修炼手册”!
提高程序运行效率的常见方法包括多进程和多线程两种,前面已经介绍了python中的多进程编程,今天来看下多线程在python中的实现。

在使用python的多线程之前,首先要理解GIL这个概念。GIL是Global Interpreter Lock的缩写,称之为全局解释器锁,是python在开发之初为了保证数据安全而设计的,每一个python进程只有一个GIL, 同一时刻,只有拿到GIL的线程可以运行,这就使得python中的多线程无法实现真正意义上的并发。所以多线程在python中的应用场景受到了限制,只适用于处理文件IO,网络IO密集型的任务。

在python中,通过内置模块threading实现多线程处理,基本用法和多进程类似,示意如下

import threadingimport urllib.request

def download_html(pathway): print('Start download kgml') url = 'http://rest.kegg.jp/get/{}/kgml'.format(pathway) out = './{}.kgml'.format(pathway) f = urllib.request.urlopen(url) with open(out, 'w') as fp: fp.write(f.read().decode('utf8'))

if __name__ == '__main__': pathway = 'hsa00010' p = threading.Thread(target = download_html, args = (pathway, )) p.start() p.join() print('Finish download kgml')

通过Thread类来定义一个线程,start方法用于启动线程,join方法用于阻塞线程。上述代码展示了用一个单独的线程来下载pathway对应的kgml文件。如果有多个pathway对应的文件要下载,用多线程的写法如下

pathways = ['hsa00010', 'hsa00020', 'hsa00030', 'hsa00040', 'hsa00051', 'hsa00052', 'hsa00053']thread_list = []for pathway in pathways:    p = threading.Thread(target = download_html, args = (pathway, ))    thread_list.append(p)    p.start()
for thread in thread_list: thread.join()

尽管多线程并不是真正意义上的并发,但是也有对应的方法来控制同时运行的最大线程数,代码如下

import threadingimport urllib.request
def download_html(pathway, semaphore): semaphore.acquire() print('Start download kgml') url = 'http://rest.kegg.jp/get/{}/kgml'.format(pathway) out = './{}.kgml'.format(pathway) f = urllib.request.urlopen(url) with open(out, 'w') as fp: fp.write(f.read().decode('utf8')) semaphore.release()
if __name__ == '__main__': pathways = ['hsa00010', 'hsa00020', 'hsa00030', 'hsa00040', 'hsa00051', 'hsa00052', 'hsa00053']
thread_list = [] semaphore = threading.BoundedSemaphore(3) for pathway in pathways: p = threading.Thread(target = download_html, args = (pathway, semaphore )) p.start() thread_list.append(p) for thread in thread_list: thread.join()
print('Finish download kgml')

多线程中变量是共享的,如果每个子进程都对同一个变量进行修改,就会出现预期之外的错误, 专业点的说法叫做产生了脏数据,示例如下

import threadingimport urllib.request
# 存钱def append_money(): global total for cnt in range(1000000): total += cnt print('total money : {}'.format(total))
# 花钱def remove_money(): global total for cnt in range(1000000): total -= cnt print('total money : {}'.format(total))

if __name__ == '__main__': total = 100 print('total money : {}'.format(total))
p1 = threading.Thread(target = append_money, args = ()) p1.start()
p2 = threading.Thread(target = remove_money, args = ()) p2.start()
p1.join() p2.join()
print('total money : {}'.format(total))

多次运行上述代码, 每次的结果会不一样

C:\Users\Administrator\Desktop>python test.pytotal money : 100total money : 340552501975total money : -33525835564total money : -33525835564
C:\Users\Administrator\Desktop>python test.pytotal money : 100total money : -55696821900total money : -197689058903total money : -197689058903
C:\Users\Administrator\Desktop>python test.pytotal money : 100total money : -260664176670total money : -245691977911total money : -245691977911

多个进程同时对一个变量进行修改,就是会存在脏数据的隐患,为此,我们需要对线程加锁,保证每次只有一个线程对变量进行修改,代码如下

import threadingimport urllib.request
def append_money(lock): lock.acquire() global total for cnt in range(1000000): total += cnt print('total money : {}'.format(total)) lock.release()

def remove_money(lock): lock.acquire() global total for cnt in range(1000000): total -= cnt print('total money : {}'.format(total)) lock.release()

if __name__ == '__main__': total = 100 print('total money : {}'.format(total)) lock = threading.Lock() p1 = threading.Thread(target = append_money, args = (lock, )) p1.start()
p2 = threading.Thread(target = remove_money, args = (lock, )) p2.start()
p1.join() p2.join()
print('total money : {}'.format(total))

添加了锁之后,就可以保证多次运行的结果都和预期保持一致了

C:\Users\Administrator\Desktop>python test.pytotal money : 100total money : 499999500100total money : 100total money : 100
C:\Users\Administrator\Desktop>python test.pytotal money : 100total money : 499999500100total money : 100total money : 100
C:\Users\Administrator\Desktop>python test.pytotal money : 100total money : 499999500100total money : 100total money : 100

实际开发中,主要采用python的多线程来完成多个url下载的任务,这种任务属于网路IO密集型,用多线程可以提高速度。如果涉及到多个线程修改同一个变量的情况,通过给线程加锁的方式来保证结果的准确性。

·end·

—如果喜欢,快分享给你的朋友们吧—



原创不易,欢迎收藏,点赞,转发!生信知识浩瀚如海,在生信学习的道路上,让我们一起并肩作战!
本公众号深耕耘生信领域多年,具有丰富的数据分析经验,致力于提供真正有价值的数据分析服务,擅长个性化分析,欢迎有需要的老师和同学前来咨询。
  更多精彩



  写在最后


转发本文至朋友圈,后台私信截图即可加入生信交流群,和小伙伴一起学习交流。


扫描下方二维码,关注我们,解锁更多精彩内容!


一个只分享干货的

生信公众号






Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/63441
 
485 次点击