Linux线程编程

Linux线程编程初步

一些历史背景

  1. Linux间接起源于Unix,而Linux诞生时并不存在 "线程"的概念。
  2. 在20世纪90年代线程才流行起来,POSIX Thread标准于 1995年确立。
  3. Unix中引入 Thread 之后,大量函数被重写,信号机制也变得复杂。
  4. 2005年之后,处理器生产厂商向超线程和多核架构靠拢。
  5. 超线程是英特尔开发出来的一项技术,使得单个处理器可以像两个逻辑处理器那样运行,这样单个处理器可以并行执行线程。

一些常见的概念

物理处理器:

安装在主机上的真实的处理器硬件。

逻辑处理器:

逻辑处理器与超线程技术相关。

        不支持超线程:逻辑处理器的数量等于核心数的数量

        支持超线程:  逻辑处理器的数量是处理器核心数的两倍

核心数:即多核处理器中的内核数量

        通过工艺手段将多个完整的 CPU 塞进一个处理器封装中(每一个 CPU 就是一个核)

线程与进程的关系

进程:

应用程序的一次加载执行(系统进行资源分配的基本单位)

线程:

进程中的程序执行流

  1. 一个进程中可以存在多个线程(至少存在一个线程)
  2. 每个线程执行不同的任务(多个线程可并行执行)
  3. 同一个进程中的多个线程共享进程的系统资源        

创建线程并不难,难的是创建的这些线程会和主线程一起往下执行。

初探线程编程模型

Linux多线程API函数

线程创建 :pthread_create

NAME
       pthread_create - create a new thread

SYNOPSIS
       #include <pthread.h>

       int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);
 

  1. pthread_t *thread :变量的地址,用于返回线程标识
  2. attr :线程的属性,可设置为NULL,即:使用默认属性
  3. start_routine :线程入口函数
  4. arg :线程入口函数参数

线程标识 :pthread_self

pthread_t pthread_self(void); 获取当前线程的 ID 标识

线程等待 :pthread_join

int pthread_join(pthread_t thread,void** retval);

等待目标线程执行结束

main.c

#include <time.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

void* thread_entry(void* arg)
{
    pthread_t id = pthread_self();
    int n = (long)arg;
    int i =0;

    while(i < n)
    {
        printf("id = %ld,i = %d\n",id,i);
        sleep(1);
        i++;
    }

    return NULL;
}

int main(int argc,char* argv[])
{
    pthread_t t1 = 0;
    pthread_t t2 = 0;
    long arg1 = 10;
    long arg2 = 5;

    pthread_create(&t1,NULL,thread_entry,(void*)arg1);
    pthread_create(&t2,NULL,thread_entry,(void*)arg2);

    printf("t1 = %ld\n",t1);
    printf("t2 = %ld\n",t2);

    pthread_join(t1,NULL);
    pthread_join(t2,NULL);


    return 0;
}

编译运行:

wj@ubuntu:~/DTThread/1-8$ gcc main.c -o main.out -lpthread
wj@ubuntu:~/DTThread/1-8$ ./main.out 
t1 = 140185317775104
t2 = 140185309382400
id = 140185317775104,i = 0
id = 140185309382400,i = 0
id = 140185309382400,i = 1
id = 140185317775104,i = 1
id = 140185317775104,i = 2
id = 140185309382400,i = 2
id = 140185317775104,i = 3
id = 140185309382400,i = 3
id = 140185317775104,i = 4
id = 140185309382400,i = 4
id = 140185317775104,i = 5
id = 140185317775104,i = 6
id = 140185317775104,i = 7
id = 140185317775104,i = 8
id = 140185317775104,i = 9

对比实验

实验一:性能对比

        相同功能的 多线程程序 vs 多进程程序的

        对比项:创建/销毁

实验二:内存共享

        多线程程序共享一段内存  ->  "全局变量"

        多进程程序共享一段内存 ->  "机制复杂"

多线程/多进程创建销毁10000次对比

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <math.h>
#include <errno.h>
#include <sched.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <pthread.h>

#define NSECS_PER_MSEC 1000000UL
#define NSECS_PER_SEC 1000000000UL
#define TEST_LOOPS 10000
#define DiffNS(begin,end) ((end.tv_sec - begin.tv_sec) * NSECS_PER_SEC \
                           + (end.tv_nsec - begin.tv_nsec))

void* thread_entry(void* arg)
{
    return NULL;
}

void thread_test()
{
    printf("thread test:\n");

    struct timespec begin = {0};
    struct timespec end = {0};
    pthread_t tid = 0;
    int i = 0;
    int diff = 0;

    clock_gettime(CLOCK_MONOTONIC,&begin);

    for(i=0;i<TEST_LOOPS;i++)
    {
        pthread_create(&tid,NULL,thread_entry,NULL);
        pthread_join(tid,NULL);
    }

    clock_gettime(CLOCK_MONOTONIC,&end);

    diff = DiffNS(begin,end) / NSECS_PER_MSEC;

    printf("result = %dms\n",diff);
}

void process_test()
{
    printf("process test: \n");

    struct timespec begin = {0};
    struct timespec end = {0};
    pid_t pid =0;
    int i=0;
    int diff =0;

    clock_gettime(CLOCK_MONOTONIC,&begin);

    for(i=0;i<TEST_LOOPS;i++)
    {
        pid = fork();

        if(pid) waitpid(pid,NULL,0);
        else return ;
    }

    clock_gettime(CLOCK_MONOTONIC,&end);

    diff = DiffNS(begin,end) / NSECS_PER_MSEC;

    printf("result = %dms\n",diff);

}

int main()
{
    thread_test();
    process_test();

    return 0;
}

编译运行:

wj@ubuntu:~/DTThread/1-7$ gcc test.c -o test.out -lpthread
wj@ubuntu:~/DTThread/1-7$ ./test.out 
thread test:
result = 652ms
process test: 
result = 1787ms
wj@ubuntu:~/DTThread/1-7$

可以看到多进程创建/销毁 更加耗时。

多线程/多进程创建销毁20000次对比

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <math.h>
#include <errno.h>
#include <sched.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <pthread.h>

#define NSECS_PER_MSEC 1000000UL
#define NSECS_PER_SEC 1000000000UL
#define TEST_LOOPS 20000
#define DiffNS(begin,end) ((end.tv_sec - begin.tv_sec) * NSECS_PER_SEC \
                           + (end.tv_nsec - begin.tv_nsec))

void* thread_entry(void* arg)
{
    return NULL;
}

void thread_test()
{
    printf("thread test:\n");

    struct timespec begin = {0};
    struct timespec end = {0};
    pthread_t tid = 0;
    int i = 0;
    int diff = 0;

    clock_gettime(CLOCK_MONOTONIC,&begin);

    for(i=0;i<TEST_LOOPS;i++)
    {
        pthread_create(&tid,NULL,thread_entry,NULL);
        pthread_join(tid,NULL);
    }

    clock_gettime(CLOCK_MONOTONIC,&end);

    diff = DiffNS(begin,end) / NSECS_PER_MSEC;

    printf("result = %dms\n",diff);
}

void process_test()
{
    printf("process test: \n");

    struct timespec begin = {0};
    struct timespec end = {0};
    pid_t pid =0;
    int i=0;
    int diff =0;

    clock_gettime(CLOCK_MONOTONIC,&begin);

    for(i=0;i<TEST_LOOPS;i++)
    {
        pid = fork();

        if(pid) waitpid(pid,NULL,0);
        else return ;
    }

    clock_gettime(CLOCK_MONOTONIC,&end);

    diff = DiffNS(begin,end) / NSECS_PER_MSEC;

    printf("result = %dms\n",diff);

}

int main()
{
    thread_test();
    process_test();

    return 0;
}

编译运行:

wj@ubuntu:~/DTThread/1-7$ gcc test.c -o test.out -lpthread
wj@ubuntu:~/DTThread/1-7$ ./test.out 
thread test:
result = 1260ms
process test: 
result = 3511ms

可以看到多进程创建/销毁更加耗时。

多线程内存共享

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <math.h>
#include <errno.h>
#include <sched.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <pthread.h>

void* thread_entry(void* arg)
{
    printf("pid = %d,ppid = %d,pgid = %d\n",getpid(),getppid(),getpgrp());

    char* shmaddr = arg;

    strcpy(shmaddr,"D.T.Software");

    return NULL;
}

int main()
{
    char* mem = malloc(128);
    pthread_t child = 0;

    printf("mem = %p\n",mem);

    if(mem == NULL)
    {
        printf("malloc error\n");
        exit(1);
    }

    int r = pthread_create(&child,NULL,thread_entry,mem);
    if(r == 0)
    {
        printf("pid = %d,ppid = %d,pgid = %d\n",getpid(),getppid(),getpgrp());

        char* shmaddr = mem;

        pthread_join(child,NULL);

        printf("%s\n",shmaddr);
    }
    else
    {
        printf("create thread error...\n");
    }

    free(mem);


    return 0;
}

编译运行:

wj@ubuntu:~/DTThread/1-7$ gcc shm-thread.c -o shm-thread.out -lpthread
wj@ubuntu:~/DTThread/1-7$ ./shm-thread.out 
mem = 0x55ffc6ee82a0
pid = 76297,ppid = 4384,pgid = 76297
pid = 76297,ppid = 4384,pgid = 76297
D.T.Software

多进程内存共享

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <math.h>
#include <errno.h>
#include <sched.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/wait.h>
#include <sys/shm.h>

#define PATH_NAME "."
#define PROJ_ID 88

int main()
{
    key_t k = ftok(PATH_NAME,PROJ_ID);
    int shmid = shmget(k,128,IPC_CREAT | S_IRWXU);

    printf("shmid = %d\n",shmid);

    if(shmid == -1)
    {
        printf("shmget error\n");
        exit(1);
    }

    int pid = fork();

    if(pid)
    {
        printf("pid = %d,ppid = %d,pgid = %d\n",getpid(),getppid(),getpgrp());

        char* shmaddr = shmat(shmid,NULL,0);

        waitpid(pid,NULL,0);

        printf("%s\n",shmaddr);
    }
    else if(pid == 0)
    {
        printf("pid = %d,ppid = %d,pgid = %d\n",getpid(),getppid(),getpgrp());

        char* shmaddr = shmat(shmid,NULL,0);

        strcpy(shmaddr,"D.T.SoftWare");

        exit(0);
    }
    else
    {
        printf("fork error...\n");
    }

    shmctl(shmid,IPC_RMID,NULL);


    return 0;
}

编译运行:

wj@ubuntu:~/DTThread/1-7$ ./shm-proc.out 
shmid = 65585
pid = 76855,ppid = 4384,pgid = 76855
pid = 76856,ppid = 76855,pgid = 76855
D.T.SoftWare

通过以上输出,可以看到是两个不同的进程,在共享同一块内存。

深入研究线程原理

Linux中线程本质

  1. 线程是后引入操作系统的概念。进程是最先引入操作系统的概念,然后是引入了线程,然后又引入了协程。从进程 -> 线程 -> 协程 来看,执行单元要越来越轻量化。
  2. 线程接口由 Native POSIX Thread Library提供,即:NPTL库函数。
  3. 线程被称为轻量级进程(Light Weighted Process)
  4. 每一个线程在内核中都对应一个调度实体,拥独立的结构体 (task_struct)
  5.  内核设计:一个进程对应内核中的一个结构体(task_struct),对应一个进程标识(PID)
  6. 引入线程:线程是内核的调度实体,在内核中必然对应一个结构体。 
  7. 内核角度:对于一个线程来说,它与一个进程的区别在哪里?就是资源不一样。

Linux中的线程本质:

用户模式:一个进程中存在多个线程

内核模式:每个线程是一个独立的调度实体。

问题:内核怎么知道某个调度实体属于哪一个进程?

另外一种视角

  1. 拥有多线程的进程,又被称为线程组(谁是线程组长?,主线程是线程组长)
  2. 在内核数据结构 task_struct 中存在 pid 和 tgid :
    1. pid_t pid -> 线程标识符 (Thread ID)
    2. pid_t tgid -> 线程组标识符 (Thread Group ID),用来存储用户模式下的进程标识
    3. 对于主线程来说,pid 等于 tgid

用户模式系统调用task_struct
线程标识符pid_t gettid(void);pid_t pid;
进程标识符pid_t getpid(void);pid_t tgid;

进程创建后默认拥有一个线程,即:主线程(默认执行流)

主线程的LWP标识符与进程标识符相同,即:主线程为线程组长(pid等于tgid)

其他子线程创建后隶属于当前进程:

        子线程的LWP标识符各不相同,且与进程标识符不同,但tgid相同

        子线程调用getpid()的结果相同(why?)

实验demo

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>
#include <sys/syscall.h>

//从内核的角度,拿到线程id,也就是内核 task_struct结构体中的 pid
pid_t gettid()
{
    return syscall(SYS_gettid);
}

void* thread_enrty(void* arg)
{
    pid_t pid = getpid(); //和 main函数中,主线程 pid 一样
    pid_t tid = gettid(); //和 mian函数中,主线程 的 tid 不一样
    pthread_t self = pthread_self();

    printf("thread:pid = %d\n",pid);
    printf("thread:tid = %d\n",tid);
    printf("thread:self = %ld\n",self);

    return NULL;
}

int main(int argc,char* argv[])
{
    pthread_t t=0;
    pthread_t self = pthread_self();
    pid_t pid = getpid();
    pid_t tid = gettid(); //主线程的 pid 等于 tgid

    printf("main:pid = %d\n",pid);
    printf("main:tid = %d\n",tid);
    printf("main:self = %ld\n",self);

    pthread_create(&t,NULL,thread_enrty,NULL);

    printf("main:t = %ld\n",t);

    pthread_join(t,NULL);

    return 0;
}

编译输出:

wj@wj:~/linux/1-7$ gcc test1.c -o test1.out -lpthread
wj@wj:~/linux/1-7$ ./test1.out 
main:pid = 10495
main:tid = 10495
main:self = 132228026910528
main:t = 132228021487296
thread:pid = 10495
thread:tid = 10496
thread:self = 132228021487296
wj@wj:~/linux/1-7$ 

思考问题

对于一个线程来说, pid_t tid = gettid();  pthread_t self = pthread_self(); 这两个函数都能够标识一个线程,那么他们之间的区别是什么呢?

线程 pid_t 是全局范围内对调度实体的唯一标识,是给内核层面使用的。

线程 pthread_t 是应用程序中对线程的局部描述,是用户层面使用的。

值得思考的问题

1.多线程之间是否有 “父子关系”?

无。进程中只有主线程和子线程,线程之间没有“父子关系”。

2.主线程如果先于子线程结束会发生什么?

Linux中主线程如果执行结束,则进程结束。进程结束则进程资源被释放,子线程被迫结束。

3.使用kill命令是否能够“杀死”制定线程?

默认情况下,kill是用来给进程发信号的(SIGTREM信号),而信号的目标是进程,是用来杀死进程的。

因此,kill任意子线程的 pid_t 将导致整个进程结束。

4.pthread_t 究竟是什么数据类型?

pthread_t是 POSIX Thread中的接口,具体定义与系统相关。

通常情况下,pthread_t的具体定义是一个无符号整型值。

        Linux中 pthread_t的定义是64位整型(保存地址值)

        其他系统中,pthread_t直接映射为 Task ID值

        对于一些特殊的系统,pthread_t是一个结构体。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

repinkply

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值