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