【Linux】线程控制-线程创建

操作系统并没有提供线程控制的相关接口,大佬分装了一套线程控制接口。

线程里我们用库函数,所以说这套接口创建的线程是用户态线程,并且这个用户态线程在操作系统中对应了一个轻量级进程。

POSIX线程库

  • 与线程有关的函数构成了⼀个完整的系列,绝⼤多数函数的名字都是以“pthread_”打头的
  • 要使⽤这些函数库,要通过引⼊头⽂
  • 链接这些线程函数库时要使⽤编译器命令的“-lpthread”选项

创建线程

功能:创建⼀个新的线程
原型
 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
参数
 thread:返回线程ID
 attr:设置线程的属性,attr为NULL表⽰使⽤默认属性
 start_routine:是个函数地址,线程启动后要执⾏的函数
 arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码

错误检查

  • 传统的⼀些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指⽰错误。
  • pthreads函数出错时不会设置全局变量errno(⽽⼤部分其他POSIX函数会这样做)。⽽是将错误代码通过返回值返回
  • pthreads同样也提供了线程内的errno变量,以⽀持其它使⽤errno的代码。对于pthreads函数的错误,建议通过返回值业判定,因为读取返回值要⽐读取线程内的errno变量的开销更⼩。
#include 
#include
#include

void* child_pthread(void* arg)
{
    while(1)
    {
        printf("i am child pthread!!\n");
        sleep(1);
    }
    return NULL;
}
int main()
{
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, child_pthread, NULL);
    if(ret != 0)
    {
        printf("crate pthread error\n");
        return -1;
    }
    while(1)
    {
        printf("i am main pthread!! pthread id:%p\n", tid);
        sleep(1);
    }
    return 0;
}

进程ID和线程ID

  • 在Linux中,目前的线程实现是Native POSIX Thread Libaray,简称NPTL。在这种实现下,线程⼜被称为轻量级进程(Light Weighted Process),每⼀个⽤户态的线程,在内核中都对应⼀个调度实体,也拥有⾃⼰的进程描述符(task_struct结构体)。
  • 没有线程之前,⼀个进程对应内核⾥的⼀个进程描述符,对应⼀个进程ID。但但是引⼊线程概念之后,情况发⽣了变化,⼀个⽤户进程下管辖N个⽤户态线程,每个线程作为⼀个独⽴的调度实体在内核态都有⾃⼰的进程描述符,进程和内核的描述符⼀下⼦就变成了1:N关系,POSIX标准⼜要求进程内的所有线程调⽤getpid函数时返回相同的进程ID,如何解决上述问题呢?
  • Linux内核引⼊了线程组的概念。
struct task_struct {
 ...
 pid_t pid;
 pid_t tgid;
 ...
 struct task_struct *group_leader;
 ...
 struct list_head thread_group;
 ...
};

多线程的进程,⼜被称为线程组,线程组内的每⼀个线程在内核之中都存在⼀个进程描述符(task_struct)与之对应。进程描述符结构体中的pid,表⾯上看对应的是进程ID,其实不然,它对应的是线程ID;进程描述符中的tgid,含义是Thread Group ID,该值对应的是⽤户层⾯的进程ID。

现在介绍的线程ID,不同于 pthread_t 类型的线程ID,和进程ID⼀样,线程ID是pid_t类型的变量,⽽且是⽤来唯⼀标识线程的⼀个整型变量。如何查看⼀个线程的ID呢?

[lyl@localhost pthread]$  ps -eLf |head -1 && ps -eLf |grep create_pthread |grep -v grep
UID         PID   PPID    LWP  C NLWP STIME TTY          TIME CMD
lyl        3320   2240   3320  0    2 17:43 pts/0    00:00:00 ./create_pthread
lyl        3320   2240   3321  0    2 17:43 pts/0    00:00:00 ./create_pthread

每个线程在虚拟地址空间中都有一份自己独有的线程地址空间存储自己的数据 ,用户态的线程id,实际上是一个虚拟地址空间的地址,线程地址空间的首地址。

pid(LWP)     tgid(PID)

可以看出上面create_pthread进程是多线程的,进程ID为3320,进程内有两个线程,线程ID分别为3320,3321。

【Linux】线程控制-线程创建_第1张图片

Linux提供了gettid系统调⽤来返回其线程ID,可是glibc并没有将该系统调⽤封装起来,在开放接⼝来共程序员使⽤。如果确实需要获得线程ID,可以采⽤如下⽅法:

#include 
 pid_t tid;
 tid = syscall(SYS_gettid);
  • 从上⾯可以看出,create_pthread进程的ID为3320,下⾯有⼀个线程的ID也是3320,这不是巧合。线程组内的第⼀个线程,在⽤户态被称为主线程(main thread),在内核中被称为group leader,内核在创建第⼀个线程时,会将线程组的ID的值设置成第⼀个线程的线程ID,group_leader指针则指向⾃⾝,既主线程的进程描述符。所以线程组内存在一个线程ID等于进程ID,而该线程为线程组的主线程。
  • ⾄于线程组其他线程的ID则有内核负责分配,其线程组ID总是和主线程的线程组ID⼀致,⽆论是主线程直接创建线程,还是创建出来的线程再次创建线程,都是这样。
  • 强调⼀点,线程和进程不⼀样,进程有⽗进程的概念,但在线程组⾥⾯,所有的线程都是对等关系。

线程ID及进程地址空间布局

  • pthread_ create函数会产⽣⼀个线程ID,存放在第⼀个参数指向的地址中。该线程ID和前⾯说的线程ID不是⼀回事。
  • 前⾯讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最⼩单位,所以需要⼀个数值来唯⼀表⽰该线程。
  • pthread_ create函数产⽣并标记在第⼀个参数指向的地址中的线程ID中,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
  • 线程库NPTL提供了pthread_ self函数,可以获得线程自身ID。
pthread_t pthread_self(void);

pthread_t 到底是什么类型呢?取决于实现。对于Linux目前实现的NPTL实现⽽⾔,pthread_t类型的线程ID,本质就是⼀个进程地址空间上的⼀个地址。

【Linux】线程控制-线程创建_第2张图片

 

你可能感兴趣的:(Linux,线程创建)