MST
星途 面试题库

面试题:Go中Goroutine与线程在简单任务场景下性能与资源占用分析

假设有一个简单的任务,比如计算1到1000000的整数之和。请分别使用Goroutine和线程(可假设使用系统原生线程相关库,如在Linux下使用pthread)来实现该任务,然后分析在这种场景下,Goroutine与线程在性能和资源占用上的差异,并且说明为什么会产生这些差异。
48.8万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

使用Goroutine实现

package main

import (
    "fmt"
)

func sum(from, to int, resultChan chan int) {
    sum := 0
    for i := from; i <= to; i++ {
        sum += i
    }
    resultChan <- sum
}

func main() {
    const numTasks = 4
    step := 1000000 / numTasks
    resultChan := make(chan int, numTasks)

    for i := 0; i < numTasks; i++ {
        from := i*step + 1
        to := (i + 1) * step
        if i == numTasks - 1 {
            to = 1000000
        }
        go sum(from, to, resultChan)
    }

    totalSum := 0
    for i := 0; i < numTasks; i++ {
        totalSum += <-resultChan
    }
    close(resultChan)

    fmt.Println("Total sum:", totalSum)
}

使用线程(以Linux下pthread为例)实现

#include <stdio.h>
#include <pthread.h>

#define NUM_THREADS 4
#define MAX 1000000

typedef struct {
    int from;
    int to;
    int sum;
} ThreadArgs;

void* sum(void* args) {
    ThreadArgs* threadArgs = (ThreadArgs*)args;
    for (int i = threadArgs->from; i <= threadArgs->to; i++) {
        threadArgs->sum += i;
    }
    pthread_exit(NULL);
}

int main() {
    pthread_t threads[NUM_THREADS];
    ThreadArgs threadArgs[NUM_THREADS];
    int step = MAX / NUM_THREADS;

    for (int i = 0; i < NUM_THREADS; i++) {
        threadArgs[i].from = i * step + 1;
        threadArgs[i].to = (i + 1) * step;
        if (i == NUM_THREADS - 1) {
            threadArgs[i].to = MAX;
        }
        threadArgs[i].sum = 0;
        pthread_create(&threads[i], NULL, sum, (void*)&threadArgs[i]);
    }

    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_join(threads[i], NULL);
    }

    int totalSum = 0;
    for (int i = 0; i < NUM_THREADS; i++) {
        totalSum += threadArgs[i].sum;
    }

    printf("Total sum: %d\n", totalSum);
    return 0;
}

性能和资源占用差异及原因分析

  1. 性能差异
    • Goroutine:在高并发场景下,Goroutine由于轻量级的特性,创建和调度开销小,能够更快速地启动大量任务。对于计算1到1000000的和这种I/O密集型任务(虽然这里主要是计算,但如果类比成更复杂计算场景类似),Goroutine的调度器可以高效地在多个Goroutine之间切换,充分利用多核CPU的性能,整体性能较好。
    • 线程:创建和销毁线程的开销较大,线程数量过多时,线程调度的开销会显著增加,导致性能下降。在这种简单计算任务中,如果线程数过多,线程上下文切换的开销会抵消并行计算带来的优势,使得性能不如Goroutine。
  2. 资源占用差异
    • Goroutine:非常轻量级,每个Goroutine只需要大约2KB的栈空间,并且栈空间可以根据需要动态增长和收缩。因此可以轻松创建数以万计的Goroutine,资源占用相对较少。
    • 线程:每个线程通常需要数MB的栈空间,创建大量线程会消耗大量内存,容易导致系统资源紧张,限制了可创建线程的数量。同时,线程的管理和调度由操作系统内核负责,内核态和用户态的切换也会带来额外的资源开销。