用户线程还是内核线程,pthread到底是哪个?

用户线程和内核线程

现代操作系统中,实现线程库有两种方法:在用户空间中和在内核空间中。

用户线程

整个线程包的实现都在用户空间的话,就意味着操作系统内核对它一无所知,只知道他是一个普通的需要调度的进程。协程就是一种用户线程的实现,可以满足在一个内核线程上并发执行多个任务,coroutine和goroutine都是其典型实现。
用户线程的优点很明显:

  1. 并发性能好(因为不用陷入内核,切换速度至少快一个数量级);
  2. 可以自定义调度算法;
  3. 扩展性较好
    但与之而来也有明显的缺点:
  4. 使用的系统调用必须是非阻塞的,因为如果使用了阻塞的系统调用,那么将陷入内核,用户线程的调度立即停止,进程下所属的所有用户线程将被停止(但是现在的linux已经可以把fd设置为非阻塞了);
  5. 线程调度无法抢占,除非某个线程主动放弃CPU,其他线程才有能获得CPU的机会。因为在进程内部没有时钟中断(内核里才有),所以无法进行轮转调度,可以做一个goroutine的实现,当一个goroutine里面死循环while(1)的时候,其他的goroutine都处于饥饿状态;
  6. (我自己的理解)如果用户线程的实现是1:N的模型,那么这样的用户级线程无法有效的利用多核(当然如果开启多个进程是可以利用多核的,但这样也就不是1:N模型了,是N:M模型)
  7. 还得自己实现调度

    内核线程

    内核线程实现的是1:1的模型,缺点在于:
  8. 切换的开销比较大(可能比用户线程高一个数量级)
  9. 只能由内核进行调度
    有点也很明显:
  10. 可以充分利用多核
  11. 不需要自己写调度

    pthread到底是内核线程库还是用户线程库

    代码测试

    前面已经有提到,内核线程可以充分利用多核,而用户线程只能通过多开进程的方式来利用多核。在Ubuntu下编写测试代码如下:
#include <pthread.h>
void* thread_func(void* ptr)
{
    while(1);
}
int main()
{
    pthread_t threads[MAX_THREAD_NUM];
    for(int i = 0; i< MAX_THREAD_NUM; i++)
    {
        pthread_create(&threads[i], NULL, thread_func, NULL);
    }

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

    return 0;
}

测试结论

测试用机是6C6T,所以操作系统可见的核心数是6(有超线程的CPU的可见核心数是12),将MAX_THREAD_NUM设置为6时,top命令结果如下:

第二行CPU的us(CPU的用户时间占比)和sy(CPU的内核时间占比)之和加起来几乎等于100,说明CPU现在几乎满载,进程的%CPU也几乎达到600%,说明6核心基本都吃满了。
将MAX_THREAD_NUM设置为5时,top命令结果如下:

可以看到CPU大致占用了5/6。结合我们这里只开启了一个进程,所以pthread库的线程不可能是用户线程,只可能是内核线程。

番外

因为Linux的调度单位是线程而不是进程,所以一个进程才能够做到同时在6个核心上运行。实际上,Linux将一个多线程的进程中的每个线程都当做单线程进程来调度,此结论参考:
Stackoverflow

猜你喜欢

转载自www.cnblogs.com/jo3yzhu/p/12368529.html