【python】python多线程为什么不适合计算密集型操作?

9 篇文章 0 订阅


一、先说是不是

不使用多线程的冒泡排序

下面的代码我们创建了五个list,并使用遍历的方法对其每一个进行冒泡排序。

import time

ARRAY_NUM = 5
ARRAY_SIZE = 5000


def bubble_sort(arr):
    for i in arr:
        for j in range(0, len(arr) - i - 1):
            if arr[j] > arr[j + 1]:
                tmp = arr[j]
                arr[j] = arr[j + 1]
                arr[j + 1] = tmp


if __name__ == '__main__':
    arrs = []
    ths = []
    for c in range(ARRAY_NUM):
        arr = []
        for d in range(ARRAY_SIZE):
            arr.append(ARRAY_SIZE - d)
        arrs.append(arr)
    time_start = time.time()
    for r in range(ARRAY_NUM):
        bubble_sort(arrs[r])
    time_end = time.time()
    print(time_end - time_start)

所需时间:

/usr/bin/python3.8 /home/loubw/PycharmProjects/tst/time_test.py
6.275976657867432

使用多线程的冒泡排序

import time
import threading

ARRAY_NUM = 5
ARRAY_SIZE = 5000


def bubble_sort(arr):
    for i in arr:
        for j in range(0, len(arr) - i - 1):
            if arr[j] > arr[j + 1]:
                tmp = arr[j]
                arr[j] = arr[j + 1]
                arr[j + 1] = tmp


if __name__ == '__main__':
    arrs = []
    ths = []
    for c in range(ARRAY_NUM):
        arr = []
        for d in range(ARRAY_SIZE):
            arr.append(ARRAY_SIZE - d)
        arrs.append(arr)
    time_start = time.time()
    for r in range(ARRAY_NUM):
        ths.append(threading.Thread(target=bubble_sort, kwargs={
            "arr": arrs[r]
        }))
        ths[r].start()
    for r in range(ARRAY_NUM):
        ths[r].join()
    time_end = time.time()
    print(time_end-time_start)

所需时间:

/usr/bin/python3.8 /home/loubw/PycharmProjects/tst/main.py
6.432690858840942

结论

python的多线程确实不适合计算密集型的(即使用cpu频繁)任务。

二、再问为什么

一、全局解释器锁

简单地说,Cpython解释器的内存管理是线程不安全的,为了避免竞争带来的错误,Cpython使用GIL来保证同一时刻只能有一个线程在执行,这就使得Cpython并不能发挥多核cpu可以并行线程的优势。

二、为什么这个多线程的例子时间达到了预期效果?

二①、不使用多线程的sleep

import time
import threading

ARRAY_NUM = 5
ARRAY_SIZE = 50000


def sleep_3s():
    time.sleep(3)


if __name__ == '__main__':
    time_start = time.time()
    for r in range(ARRAY_NUM):
        sleep_3s()
    time_end = time.time()
    print(time_end-time_start)

所需时间:

/usr/bin/python3.8 /home/loubw/PycharmProjects/tst/time_test_sleep.py
15.009067058563232

二②、使用多线程的sleep

import time
import threading

ARRAY_NUM = 5
ARRAY_SIZE = 50000


def sleep_3s():
    time.sleep(3)


if __name__ == '__main__':
    ths = []
    time_start = time.time()
    for r in range(ARRAY_NUM):
        ths.append(threading.Thread(target=sleep_3s))
        ths[r].start()
    for r in range(ARRAY_NUM):
        ths[r].join()
    time_end = time.time()
    print(time_end-time_start)

所需时间:

/usr/bin/python3.8 /home/loubw/PycharmProjects/tst/time_test_sleep_mthread.py
3.003570079803467

二③、结论

在进行IO密集型(线程处于阻塞情况比较多)的任务时,可以有效利用cpu,接下来我们将简介什么是
线程的阻塞状以及为什么我们在这里仅仅用了sleep充当计算机IO就可以下结论说进行IO任务多线程可以有效利用cpu。


三、多线程在C语言中的表现

三①、C语言中不使用多线程的冒泡排序

#include "stdio.h"
#include "pthread.h"
#include "stdlib.h"
#include "sys/time.h"
#define ARRAY_SIZE 50000
#define ARRAY_NUM 5
typedef struct {
    int* arr;
    int size;
} num_array;

void *BubbleSort(void *data)
{
    num_array *p = (num_array *)data;
    int *arr = p->arr;
    int size = p->size;
    int i, j, tmp;
    for (i = 0; i < size - 1; i++) {
        for (j = 0; j < size - i - 1; j++) {
            if (arr[j] > arr[j+1]) {
                tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
            }
        }
    }

    return NULL;
}

double get_us(struct timeval t){
    return (double )(t.tv_sec*1000000 + t.tv_usec);
}

int main(){
    int arr[ARRAY_NUM][ARRAY_SIZE];
    num_array *a_s[ARRAY_NUM];

    for (int j=0;j<ARRAY_NUM;j++){
        for(int i=0;i<ARRAY_SIZE;i++){
            arr[j][i] = ARRAY_SIZE-i;
        }
        a_s[j] = malloc(sizeof(num_array)) ;
        a_s[j]->size = ARRAY_SIZE;
        a_s[j]->arr = arr[j];
    }

    clock_t begin = clock();
    struct timeval start_time,end_time;
    gettimeofday(&start_time,NULL);
    for(int i=0;i<ARRAY_NUM;i++){
        BubbleSort((void *)a_s[i]);
    }
    gettimeofday(&end_time,NULL);
    clock_t end = clock();
    double time_consumption = (double)(end - begin) / CLOCKS_PER_SEC;
    printf("Not Use MThread Time use %f ms\n", (get_us(end_time)- get_us(start_time))/1000);
    printf("Not Use MThread cpu time use %f s\n", time_consumption);
}

所需时间:

loubw@loubw-ThinkCentre-M920t-N000:~/CLionProjects/untitled/torp$ ./a.out 
Not Use MThread Time use 19217.993000 ms
Not Use MThread cpu time use 19.214332 s

三②、C语言中使用多线程的冒泡排序

#include "stdio.h"
#include "pthread.h"
#include "stdlib.h"
#include "sys/time.h"
#define ARRAY_SIZE 50000
#define ARRAY_NUM 5
typedef struct {
    int* arr;
    int size;
} num_array;

pthread_t ths[ARRAY_NUM];

void *BubbleSort(void *data)
{
    num_array *p = (num_array *)data;
    int *arr = p->arr;
    int size = p->size;
    int i, j, tmp;
    for (i = 0; i < size - 1; i++) {
        for (j = 0; j < size - i - 1; j++) {
            if (arr[j] > arr[j+1]) {
                tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
            }
        }
    }

    return NULL;
}

double get_us(struct timeval t){
    return (double )(t.tv_sec*1000000 + t.tv_usec);
}

int main(){
    int arr[ARRAY_NUM][ARRAY_SIZE];
    num_array *a_s[ARRAY_NUM];

    for (int j=0;j<ARRAY_NUM;j++){
        for(int i=0;i<ARRAY_SIZE;i++){
            arr[j][i] = ARRAY_SIZE-i;
        }
        a_s[j] = malloc(sizeof(num_array)) ;
        a_s[j]->size = ARRAY_SIZE;
        a_s[j]->arr = arr[j];
    }

    clock_t begin = clock();
    struct timeval start_time,end_time;
    gettimeofday(&start_time,NULL);
    for(int i=0;i<ARRAY_NUM;i++){
        pthread_create(&(ths[i]), NULL, BubbleSort, (void *)a_s[i]);
    }
    for(int i=0;i<ARRAY_NUM;i++){
        pthread_join(ths[i], NULL);
    }
    gettimeofday(&end_time,NULL);
    clock_t end = clock();
    double time_consumption = (double)(end - begin) / CLOCKS_PER_SEC;
    printf("Use MThread Time use %f ms\n", (get_us(end_time)- get_us(start_time))/1000);
    printf("Use MThread cpu time use %f s\n", time_consumption);
}

所需时间:

loubw@loubw-ThinkCentre-M920t-N000:~/CLionProjects/untitled/torp$ ./a.out 
Use MThread Time use 3936.645000 ms
Use MThread cpu time use 19.521638 s

三③、结论

  • 多线程在C语言发挥了自己的作用,原本需要19.2s的程序在多线程的运行下减少到了3.9s。
  • 注意输出的时间中的第二个是使用的cpu时间,而不是我们所说的时间。
  • 在实践的过程中,我对比了python和c进行五个50000个数字的数组的冒泡排序,C需要19s,而python居然需要600s
    我检查完程序认为自己没写错,没办法就把python的50000改成了5000。如果您的测试中python不需要这么多时间或者发现了我的程序错误,评论通知我,感谢。

线程的五种状态

  • 新建状态
    创建一个线程。
  • 就绪状态
    线程完成创建,等待进入cpu执行。
  • 运行状态
    得到cpu的执行权,进入cpu执行,在cpu的一个时间片执行完毕后,线程或进入就绪状态或进入阻塞状态。
  • 阻塞状态
    阻塞状态下线程让出cpu,等阻塞状态结束后进入就绪状态等待cpu调度。
    阻塞大概有以下几种情况
    • 主线程调用join等待子线程结束时,主线程会进入阻塞状态。
    • 等待IO时,比如c的read()(等待用户输入)。
    • sleep()也会导致现线程进入阻塞(C、python)。
      这就是为什么上面的sleep获得了理想的执行时间,而IO操作也是同理。
  • 死亡状态
    线程执行完毕。

三、什么是线程?

大概理解一下,要知道线程的具体执行原理还需要继续看书。

一、是什么

线程本质上就是函数的执行。每个进程总会有一个线程,对于C语言程序来说就是main函数为主线程。

二、与进程的区别

每个进程都有自己的地址空间和(通常)一个线程,在C程序编译为可执行文件后,执行此文件,此时在内存中这个进程具有它自己的

  • 文本段:存放CPU需要执行的机器指令,通常是所有线程共享但只读的
  • BSS段:存放未初始化的全局和静态变量
  • 数据段:存放初始化过的全局和静态变量
  • 堆区:用于动态内存分配,线程共享
  • 栈区:存放函数的参数、返回、局部变量以及该函数使用的寄存器信息等,线程不共享

但进程虽然管理了程序运行的所有资源,但把main函数中的一条条指令放到cpu执行的却是它中的主线程。
也就是说,线程把需要执行的代码放到了寄存器,CPU调度执行。

三、线程的共享资源

文本段、数据段、BSS段、堆区

四、线程的独占资源

栈区、函数运行需要的寄存器、程序计数器(是一个寄存器,通过恢复程序计数器就知道接下来要执行哪个指令)、栈指针

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值