多线程一定更快吗

在《多线程排序》中介绍了多线程排序,似乎看起来多线程快了很多,然而多线程就一定更快吗?

为什么多线程就不一定快?

还是拿《多线程排序》中的程序举例,下面是各个线程数量的排序结果:

线程数 时间/s
1 2.393644
2 1.367392
3 1.386448
4 1.036919
5 1.097992
6 1.218000
7 1.184615
8 1.176258

以上结果可能不准确,但是体现了一些变化趋势,即并不是线程数量越多越快,也不是单线程最快,而是线程数为4的时候最快。

为什么呢?

原因在于我的机器只有4个逻辑CPU,因此4是最合适的。为了不解释太多术语,简单解释一下。一个CPU就像一条流水线,会执行一系列指令,当你很多指定拆成4份(4线程)的时候,它是正好最合适的,少的时候,有一个闲着;而多了,就会存在抢占的情况。举个简单的例子,假设有4个水管可以出水,你现在去接水,那么你在每个水管下放一个桶去接水,自然要比只在一个水管下去接水要快的,但是如果你的水桶数量多于水管数,为了每个水桶都要有水,你在这个过程中就需要去切换水桶,每个水桶换一下,才能都接得上,而换的这个过程就像线程的上下文切换带来的开销

因此,并不是线程越多越快,最合适的才最快。

单线程有时候反而更快

说到这你可能更会奇怪了,为什么单线程有时候反而会更快呢?还是拿接水为例,假设虽然有4个水管,但是你只有一个桶,因此你一个人从这个水管里一直接水是最快的,而如果你拿两个桶,这个接一点,又换一下,那个接一点,又换一下,中间显然有中断,相同时间内单个桶接的比较多;这就是单核CPU妄图使用多线程提高效率或者每个线程都需要竞争同一把锁而实际可能会导致更慢的缘故。

举个绑核的例子:

1
2
3
$ taskset -c 1 taskset -c 1 ./multiThread 4
thread num:4
time 2.378558

我使用taskset将程序绑定在一个CPU上运行,可以看其时间足足是不绑核的时候的两倍有余。

什么叫都需要竞争呢?举个极端的例子,我们修改前面的工作线程代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
/*比较线程,采用快速排序*/
void * workThread(void *arg)
{
pthread_mutex_lock(&mutex);
SortInfo *sortInfo = (SortInfo*)arg;
long idx = sortInfo->startIdx;
long num = sortInfo->num;
qsort(&nums[idx],num,sizeof(long),compare);
pthread_mutex_unlock(&mutex);
pthread_barrier_wait(&b);
return ((void*)0);
}

这里的例子比较极端,在排序的时候都给它们加上了锁(关于锁,后面会有文章进行更加详细的介绍。),即哪个线程拿到了锁,就可以继续工作,没有拿到的继续等待。使用完成后再释放。
在这样的情况下,看看4线程还有效果吗?

1
2
3
$ ./multiThread 4
thread num:4
time 2.480588

是最快的时候两倍多的时间!而且还比单个线程的时候要慢!!!

而另外一种情况,比如说从队列中取出数据,然后进行耗时处理,那么对取出数据的操作进行加锁是可行的,多线程的情况仍然能提高处理速度。但如果你仅仅是读取数据,那么单线程的情况可能会比多线程要快,因为它避免了线程上下文切换的开销

扩展介绍-绑核

为什么要绑核?

  • 充分利用CPU,减少CPU之间上下文切换
  • 指定程序运行在指定CPU,便于区分
1
$ taskset -c 1 ./proName

将proName绑定在第二个核。

1
$ taskset -c 1-3 ./proName

绑定运行在第二个到第四个核。

1
2
$ taskset -p 3569
pid 3569's current affinity mask: f

查看进程3569当前运行在哪个核上。

mask f转为二进制即为1111,因此四个核都有运行。

当然除了命令行,还有函数接口可以使用,这里就不再扩展了。

如何查看机器的CPU数量

物理CPU个数,就是你实际CPU的个数:

1
2
$ cat /proc/cpuinfo | grep "physical id" | sort -u | wc -l
1

CPU物理核数,就是你的一个CPU上有多少个核心,现在很多CPU都是多核:

1
2
$ cat /proc/cpuinfo | grep "core id" | sort -u | wc -l
2

CPU逻辑核数,一颗物理CPU可以有多个物理内核,加上超线程技术,会让CPU看起来有很多个:

1
2
$ cat /proc/cpuinfo | grep "processor" | sort -u | wc -l
4

总结

线程上下文切换是有开销的,如果它的收益不能超过它的开销,那么使用多线程来提高效率将得不偿失。因此不要盲目推崇多线程。如果为了提高效率采用多线程,那么线程中最多应为逻辑CPU数。也就是说如果你的程序绑在一个核上或者你只有一个CPU一个核,那么采用多线程只能提高同时处理的能力,而不能提高处理效率。

守望 wechat
关注公众号[编程珠玑]获取更多原创技术文章
出入相友,守望相助!