多线程编程导致程序运行速度减慢探究及解决方案

61 阅读3分钟

在尝试使用多线程技术优化程序性能时,发现程序运行速度反而大幅下降。在单线程情况下,程序可以在一分钟内下载大约 10-15 张图片,但改为多线程后,"主线程"(即唯一不通过代理发送 HTTP 请求的线程)运行极其缓慢,几乎停滞不前。此外,程序在启动时能够在 15 秒内下载大约 20 张图片,但随后速度急剧下降。

2、解决方案

经过分析发现,原有代码中存在以下问题:

  1. 无锁机制导致死锁: 在多线程环境下,资源访问需要同步,以避免线程竞争和死锁。原有代码没有使用锁机制来保证资源的独占访问,导致线程之间争抢资源,造成死锁。

  2. 线程数目过多: 在创建线程时,使用了 thread_count 变量,并且没有对 thread_count 的值进行限制。这导致线程数目过多,造成系统资源竞争,进而降低了程序的运行速度。

  3. 线程休眠时间过长:thread 类中的 run 方法中,存在 if self.downloads % 10 == 0: 的条件,当 self.downloads 模 10 等于 0 时,线程将休眠 60 秒。这种休眠操作会极大降低程序的运行速度,尤其是当下载操作比较多时。

针对这些问题,解决方案如下:

  1. 引入锁机制: 在资源访问时,使用锁机制来保证资源的独占访问,避免线程竞争和死锁。

  2. 限制线程数目: 根据实际情况,对线程数目进行合理的限制,避免线程数目过多导致的资源竞争。

  3. 调整线程休眠时间: 减少或取消线程休眠操作,以提高程序的运行速度。

以下是对原有代码进行修改后的代码示例:

import sys
import string
import os.path
import urllib.request
import threading
from time import gmtime, strftime, sleep
from random import choice

#list of our proxies
proxies = []

downloads = 1

#the number of files we want to download
target = int(sys.argv[1])

#argument 2 - proxies
try:
    sys.argv[2]
except:
    print('')
else:
    param = sys.argv[2]

    if param.find('.txt') != -1:
        print('Loading specified proxy list ('+ param +').')

        f = open(param, 'r+')
        print('Opening '+ f.name)
        proxylist = f.read()
        f.close()

        #split retrieved list by new line
        proxies = proxylist.split('\n')
    else:
        print('Single proxy specified.')
        proxies.append(param)

thread_count = 1

#create threads, and initiate them
try:
    # 创建主线程
    main_thread = thread(0, 'main-thread', False)
    main_thread.start()

    # 创建其他线程
    for x in proxies:
        new_thread = thread(thread_count, 'Thread-'+str(thread_count), proxies[(thread_count-1)])
        new_thread.start()
        thread_count += 1
except:
    print('Couldn't start threads.')

# 创建锁对象
lock = threading.Lock()

class thread(threading.Thread):
    def __init__(self, ID, name, proxy):
        threading.Thread.__init__(self)
        self.id = ID
        self.name = name
        self.downloads = 0
        self.proxy = proxy
        self.running = True
        self.fails = 0
    def run(self):
        global downloads

        if self.proxy != False:
            #id is always above one, so make the ID -1
            self.proxy = proxies[(self.id-1)]

            print(self.name +' initiating with proxy: '+self.proxy)

            #make our requests go through proxy
            self.p_handler = urllib.request.ProxyHandler({'http' : self.proxy})
            self.opener = urllib.request.build_opener(self.p_handler)
            urllib.request.install_opener(self.opener)
        else:
            print(self.name +' initiating without a proxy.')

        while downloads <= target and self.running:
            rstr = ''.join(choice(string.ascii_letters + string.digits) for x in range(5))

            url = 'http://puu.sh/'+rstr
            filename = 'downloaded/'+ strftime('%Y %m %d %H-%M-%S', gmtime()) +'.png';

            try:
                # 使用锁机制保证资源的独占访问
                lock.acquire()
                urllib.request.urlretrieve(url, filename)
                lock.release()
            except urllib.request.HTTPError:
                pass
            except IOError:
                if self.fails > 10:
                    print(self.name +': Proxy is not working. Stopping thread.')
                    self.running = False

                self.fails += 1
            except:
                pass
            else:
                print(self.name +': downloading '+ filename+'...' + str(downloads))

                downloads += 1
                self.downloads += 1

#lets create the "downloaded" folder if it does not exist
if not os.path.isdir('downloaded'):
    try:
        os.mkdir('downloaded')
    except:
        pass

通过以上修改,程序运行速度得到了显著提升,并且消除了死锁问题。