用户线程与内核线程的区别?


先补充下基础知识:


进程&线程


进程是应用程序执行的“实例”,它维护着应用程序所需的各种资源;线程是应用程序执行的“实体”(我的理解是真正干活的部分),所以要想让进程完成一定的工作,其必须至少包含一个线程。


然而,一直以来, linux内核并没有线程的概念.。每一个执行实体都是一个task_struct结构(进程)。通过系统调用clone创建子进程时,可以有选择性地让子进程共享父进程所引用的资源。这样的子进程通常称为轻量级进程。


linux上的线程就是基于轻量级进程,由用户态的pthread库实现的。使用pthread以后,在用户看来,每一个task_struct就对应一个线程,一组线程以及它们所共同引用的一组资源被视为一个整体后就是一个进程。


Linux线程库-pthread


在Linux内核版本2.6之前,pthread使用的是一个叫linuxthreads的库,其通过轻量级进程来实现线程,特点是:

(1).通过在首次调用pthread_create时自动创建一个管理线程,并通过管理线程实现对其他子线程的创建与管理。

(2).创建与销毁需要一次进程间通信,一次上下文切换之后才能被管理线程执行,并且多个请求会被管理线程串行地执行。


到了linux内核版本2.6, glibc中有了一种新的pthread线程库--NPTL(Native POSIX Threading Library)


从Linux 2.6以后,内核有了线程组的概念,task_struct结构中增加了一个tgid(thread group id)字段。如果这个task是一个"主线程",则它的tgid等于pid,否则tgid等于其所属进程的pid,在clone系统调用中, 传递CLONE_THREAD参数就可以把新进程的tgid设置为父进程的tgid(否则新进程的tgid会设为其自身的pid);getpid(获取进程ID)系统调用返回的是tast_struct中的tgid,这样解释了实例11-1在Ubuntu12.04中的执行结果(详见:apue读书笔记-第11章 线程)


回到问题本身,上面所述的两种线程库实现使用的都是内核级线程(每个线程都对应内核中的一个调度实体),这种模型称为1:1模型(1个线程对应1个内核级线程);这里要引入一种新的线程库实现NGPT(Next Generation POSIX Threads),

其打算实现M:N模型(M个线程对应N个内核级线程),也就是说若干个线程可能是在同一个执行实体上实现的。这就要求

线程库需要在一个内核提供的执行实体上抽象出若干个执行实体,并实现它们之间的调度.。这样被抽象出来的执行实体称为用户级线程用户级线程的切换显然要比内核级线程的切换快一些, 前者可能只是一个简单的长跳转, 而后者则需要保存/装载寄存器,,进入然后退出内核态。但是用户级线程不能享受多处理器, 因为多个用户级线程对应到一个内核级线程上, 一个内核级线程在同一时刻只能运行在一个处理器上。(当前Linux内核尚未实现)


参考:Linux线程浅析


习题12.2

实现putenv_r,即putenv的可重入版本。确保即是线程安全,也是异步信号安全的

#include <pthread.h>
#include <stdlib.h> // getenv
#include <stdio.h>
#include <string.h>
#include <unistd.h> //environ
extern char **environ; 
pthread_mutex_t mutex;
static pthread_once_t init_done = PTHREAD_ONCE_INIT;
static void thread_init(void)
{
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&mutex, &attr);
    pthread_mutexattr_destroy(&attr);
}
int putenv_r(char *string)
{
    int i, len;
    char *ptr_key = NULL; 
    ptr_key = strchr(string, '='); // 指向字符串中首次出现'='的地址
    if (NULL == ptr_key)
    {
        printf("Param illegal Usage: name=value\n");
        exit(0);
    }
    len = ptr_key - string; // 地址的差即name的长度
    pthread_once(&init_done, thread_init);
    pthread_mutex_lock(&mutex);
    for (i = 0; NULL != environ[i]; i++)
    {
        if (0 == strncmp(string, environ[i], len))
        {
            environ[i] = string; // 若已存在,覆盖
            pthread_mutex_unlock(&mutex);
            return 0;
        }
    }
    environ[i] = string;
    pthread_mutex_unlock(&mutex);
    return 0;
}
int main(int argc, char const *argv[])
{
    const char *STRING = "EDITOR=vim";
    const char *KEY = "EDITOR";
    char *ptr = NULL;
    char *value = NULL;
    ptr = malloc(sizeof(char) * strlen(STRING) + 1);
    if (NULL == ptr)
    {
        printf("malloc failed!\n");
        exit(0);
    }
    strcpy(ptr, STRING);
    putenv_r(ptr);
    value = getenv(KEY);
    if (NULL != value)
    {
        printf("Set %s to %s\n", value, KEY);
    }
    return 0;
}