python日记(一):为什么我的多线程速度反而不如单线程?

Background(废话,可以跳过不看)

今天使用在实际操作中,需要求一个矩阵中每个结点对之间相似度,矩阵的大小大概有17000+,所以一共需要计算结点对17000×17000=?????辣么老多个。计算了一下大概需要三个小时以上。
然后就想到了以前用过的多线程爬虫,简直爽到飞起,所以想着用多线程可以是不是可以提高循环计算的速度?然后就动手写了一个多线程,但是发现速度甚至不及单线程,甚至还要更慢。Excuse Me????
然后就去查看了一下别的大佬怎么讲。

python线程原理(敲黑板)

下面引入一个概念GIL。我们看官方给出的解释:

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.)
这个东西本身就是一个全局的线程锁,因为python的内存管理不是线程安全的,所以GIL被用来防止多个本机线程同时执行Python字节码。简单明了地说,python好像对多线程是不太友好的。
接下来我们使用代码去验证:

weights = []
 row = []
 col = []
 lock = threading.Lock()   # 申请线程锁
 time1 = time.time()
 def thread_cal_cosine(start,end):
     for i in list(struct_vocab.keys())[start:end]:
         for j in list(struct_vocab.keys()):
             if i == j:   # 跳过自环
                 continue
             weight = 1 - cosine(struct_vocab[i], struct_vocab[j])   # 1-余弦距离才是余弦相似度
             if weight > 0.9:   # 这个参数是TextGCN里提到的
                 lock.acquire()   # 变量加锁
                 weights.append(weight)
                 row.append(i)
                 col.append(j)
                 lock.release()
 threads = []
 for index in range(0, 20, 5):
     start = index
     end = index + 5
     threads.append(threading.Thread(target=thread_cal_cosine, args=(start, end)))
 for each in threads:
     each.start()
     each.join()
     print('线程' + str(each) + '执行')

 print('len weights:', len(weights))
 struct_adj = sp.csr_matrix(
     (weights, (row, col)), shape=(len(vocabs), len(vocabs)))
 # io.mmwrite(path + '{}.struct.adj.mtx'.format(dataset), struct_adj)
 # print(path + '{}.struct.adj.mtx'.format(dataset), struct_adj)   # 稀疏矩阵特殊的存放数据方法
print('用时:', time.time() - time1)

我为结点之间相似度的计算添加了线程。为了执行效果快,我只截取了第一个for循环的前20个,实际上有17000+,最终用时:

用时: 12.304814100265503

接下来再来看单线程的:

time1 = time.time()
for i in list(struct_vocab.keys())[0:20]:
    for j in list(struct_vocab.keys()):
        if i == j:   # 跳过自环
            continue
        weight = 1 - cosine(struct_vocab[i], struct_vocab[j])   # 1-余弦距离才是余弦相似度
        if weight > 0.9:   # 这个参数是TextGCN里提到的
            weights.append(weight)
            row.append(i)
            col.append(j)

print('len weights:', len(weights))
struct_adj = sp.csr_matrix(
    (weights, (row, col)), shape=(len(vocabs), len(vocabs)))
# io.mmwrite(path + '{}.struct.adj.mtx'.format(dataset), struct_adj)
# print(path + '{}.struct.adj.mtx'.format(dataset), struct_adj)   # 稀疏矩阵特殊的存放数据方法
print('用时:', time.time() - time1)
用时: 12.467553615570068

这根本没差嘛!从原理上解释,尽管CPU是多核的,理论上可以执行多线程的任务。但是由于python GIL的机制,多线程并无法抢占多余的计算资源,并且在线程调度的过程中,也可能造成时间的浪费。这就使得在多线程执行的时候,效果甚至可能比单线程要慢!!!!
但是,以上缺陷只对于计算密集型任务来说,对于爬虫这种请求密集或是文件读写这种IO密集的任务,多线程还是能很好地胜任的。因为每个线程执行的时候都是在请求外部的资源,而非CPU内部的计算,线程执行之后需要很久,外部任务(比如HTTP请求)才会完成,CPU有充分的时间完成线程的切换。
所以,一句话总结:
在进行数值计算的时候,python的多线程很拉跨;在进行网络爬虫,文件读写的时候,多线程又能排上很大用场。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

五月的echo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值