unix系统编程day06--Linux线程讲解

线程概念

什么是线程

  • LWP:light weight process轻量级进程,本质仍是进程(在Linux环境下)
  • 进程:独立地址空间,拥有PCB
  • 线程:也有PCB,但没有独立的地址空间(共享)
  • 区别:在于是否共享地址空间。 独居(进程);合租(线程)
  • Linux下:线程:最小执行单位,进程最小分配资源单位,可看成只有一个线程的进程

Linux内核线程实现原理

类unix系统中,早期是没有线程的概念的,80年代才引入,借助进程机制实现了线程的概念,因此在这类系统中,进程和线程关系密切。

  1. 轻量级进程(LWP),也有PCB,创建线程和进程的底层函数一样,都是clone函数
  2. 从内核看进程和线程都是一样的,都有不同的PCB,但是PCB中的三级页表是相同的。
  3. 进程可以蜕变成线程
  4. 线程可以看作寄存器和栈的集合
  5. 在linux下,线程是最小的执行单元,进程是最小的资源分配单元
    查看LWP号:ps -Lf pid用来查看指定进程的LWP号

线程号和线程ID的区别:线程号是cpu分配时间轮片的依据,线程ID是进程区分线程的依据

补充:MMU的工作过程,首先从PCB的三级页表指针找到页目录,然后通过页目录找到页表,通过页表找到物理单元。

线程共享资源

  1. 文件描述符表
  2. 每种信号的处理方式
  3. 当前工作目录
  4. 用户ID和组ID
  5. 内存地址空间(.data/.bss/.text/headp/共享库)【除掉栈区】

线程非共享资源

  1. 线程ID
  2. 处理器现场(寄存器)和栈区(内核栈)
  3. 独立的栈空间(用户栈)
  4. errno变量
  5. 信号屏蔽字
  6. 调度优先级

线程优缺点

优点:提高程序的并发性、开销小(相对进程)、共享数据方便
缺点:库函数不稳定、编写调试难不能用GDB、对信号支持不好

线程控制原语

pthread_self函数

  • 介绍:获取线程ID(不是LWP号),对应进程中的getpid();
  • 函数原型:pthread_t pthread_self(void)
  • 返回值:返回对应的线程ID

pthread_create函数

  • 介绍:创建一个新线程,对应进程的fork函数
  • 函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg
  • 返回值:成功返回0,失败返回错误号
  • 错误号处理:使用char * strerror(int errnum)来获取错误信息并输出

代码演示:

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

void * t_fun(void * arg) {
    printf("In t_fun thread id = %lu, pid = %lu\n", (unsigned long)pthread_self(), (unsigned long)getpid());
    return NULL;
}

int main(void) {
    pthread_t tid;
    printf("In t_fun thread id = %lu, pid = %lu\n", (unsigned long)pthread_self(), (unsigned long)getpid());
    int ret = pthread_create(&tid, NULL, t_fun, NULL);
    if(ret != 0) {
        fprintf(stderr, "pthread_create failed: %s\n", strerror(ret));
        exit(1);
    }
    sleep(1); 
    printf("In t_fun thread id = %lu, pid = %lu\n", (unsigned long)pthread_self(), (unsigned long)getpid());
    
    return 0;
}

循环创建子线程代码:

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

void * t_fun(void * arg) {
    long long int i = (long long int )arg;
    sleep(i);
    printf("%lldth thread id = %lu, pid = %lu\n", i, (unsigned long)pthread_self(), (unsigned long)getpid());
    return NULL;
}

int main(void) {
    pthread_t tid;
    for(int i = 0; i < 5; i++) {
        int ret = pthread_create(&tid, NULL, t_fun, (void *)i);
        if(ret != 0) {
            fprintf(stderr, "pthread_create failed: %s\n", strerror(ret));
            exit(1);
        }
    }
    sleep(5); 
    
    return 0;
}

线程与共享

线程之间共享全局变量:

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

int var = 100;

int main(void) {
    pthread_t tid;
    printf("The first var = %d\n", var);
    auto f = [](void *arg) mutable -> void* {var = 200; return NULL;};
    int ret = pthread_create(&tid, NULL, f, NULL);
    if(ret != 0) {
        fprintf(stderr, "create failed: %s", strerror(ret));
        exit(1);
    }
    sleep(1);
    printf("after the var = %d\n", var);
    return 0;
}

pthread_exit函数

  • 函数原型:void pthread_exit(void *value_ptr);
  • 作用:退出当前线程
  • 注意:在线程中,exit、return、pthread_exit拥有完全不同的含义,其中return是使函数返回到调用函数的地方,exit为结束整个进程,pthread_exit是结束当前线程,所以在多线程编程中推荐使用pthread_exit函数

pthread_join函数

阻塞线程退出,获取线程退出状态,对应进程的wait函数

  • 函数原型:int pthread_join(pthread_t thread, void ** value_ptr);
  • 作用:回收线程,并把线程的返回值出存在value_ptr中
  • 返回值:成功返回0,失败返回错误代码

代码演示:

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <memory.h>
using std::shared_ptr;
using std::make_shared;

struct exit_t {
    exit_t(char _c, int _val, char *_str) : c(_c), val(_val) {
        strcpy(str, _str);
    };
    char c;
    int val;
    char str[200];
};

void *t_fun(void *arg) {
   exit_t * exit_val = (exit_t *) arg; 
   exit_val->c = 'z';
   exit_val->val = 200;
   strcpy(exit_val->str, "zhang");
   pthread_exit((void *)exit_val);
}

int main(void) {
    //exit_t *exit_val = (exit_t*)malloc(sizeof(exit_t));
    shared_ptr<exit_t> exit_val = make_shared<exit_t>(exit_t('f', 1000, "hahaha"));
    pthread_t tid = 0;
    int ret = pthread_create(&tid, NULL, t_fun, (void *)&exit_val);
    if(ret != 0) {
        printf("create error: %s\n", strerror(ret));
        exit(1);
    }
    sleep(1);
    pthread_join(tid, (void **) & exit_val);
    printf("exit->c=%c, exit->val=%d, exit->str=%s\n", exit_val->c, exit_val->val, exit_val->str);
    return 0;
}

pthread_detach函数

将线程在状态层面上与主线程分离,常用于服务器编程和网络编程。

  • 函数原型:int pthread_detach(pthread_t tid);
  • 作用:将子线程与主线程分离(状态上,并没有新的内存地址空间)
  • 返回值:成功返回0,失败返回错误编号

代码演示

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

int main() {
    auto f = [](void *arg) mutable -> void* { 
        int n = 3;
        while(n--) {
            printf("%d\n", n);
            sleep(1);
        }
        pthread_exit((void *)1);
    };
    pthread_t tid = 0;
    pthread_create(&tid, NULL, f, NULL);
    pthread_detach(tid);
    while(1) {
        int ret = pthread_join(tid, NULL);
        if(ret != 0) {
            printf("error num: %d\n", ret);
            printf("join error: %s\n", strerror(ret)); 
        }
        sleep(1);
    }
    return 0;;
}

pthread_cancel函数

用来杀死线程,但是有一定的延时性,需要到达固定的取消点(检查点)才能杀死线程。

终止线程方式

  1. 使用return结束回调函数
  2. 使用pthread_exit函数
  3. 使用pthread_cancel函数

注意事项

pthread函数具有一定的延时性,比如玩游戏必须要到某个地方(客栈等)的存档点才能存档,在Linux中可以简单的认为系统函数中都会有一个cancel检查点,如果在函数体中没有检查点,那么需要手动调用pthread_testcancel函数来设置检查点发,但是有一定的延时性。

代码演示

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

int main(void) {
    void *(*f)(void*arg) = [](void *arg) -> void*{
        int i = 0;
        while(1) {
            printf("xxxlife: %d\n", i++);
            //sleep(1);
            pthread_testcancel();
        }
    };  
    pthread_t tid;
    pthread_create(&tid, NULL, f, NULL);
    sleep(3);
    pthread_cancel(tid);
    int ret = pthread_join(tid, NULL);
    if(ret != 0) {
        printf("join error: %s\n", strerror(ret)); 
    }
    return 0;
}

pthread_equal函数

在目前的Linux系统中用处并不大,应为pthread_t为整型数据所以可以使用“==”来判断,但是在未来的Linux系统中可能会对pthread_t进行拓展。

  • 函数原型:int pthread_equal(pthread_t t1, pthread_t t2);
  • 作用:查看两个线程是否相同
  • 返回值:如果相同则返回非零,否则返回0

控制原语对比

进程线程
forkpthread_create
exitpthread_exit
waitpthread_join
killpthread_cancel
getpidpthread_self
无分离函数pthread_detach

线程属性

结构体原型:

typedef struct
{
    int                           detachstate;     线程的分离状态
    int                          schedpolicy;   线程调度策略
    struct sched_param      schedparam;   线程的调度参数
    int                          inheritsched;    线程的继承性
    int                          scope;          线程的作用域
    size_t                      guardsize; 线程栈末尾的警戒缓冲区大小
    int                          stackaddr_set;
    void *                     stackaddr;      线程栈的位置
    size_t                      stacksize;       线程栈的大小
}pthread_attr_t;          

我们对于线程属性暂时掌握常用的三个属性:

  1. 设置线程的分离状态,使用pthread_detach函数也可以做到
  2. 设置线程栈大小
  3. 设置线程警戒区的大小
    什么事警戒区:警戒区为内核栈中两个栈帧之间的区域,为了防止栈溢出

线程属性初始化

pthread_init函数

  • 函数原型:int pthread_init(pthread_attr_t * attr);
  • 作用:初始化一个线程属性
  • 返回值:成功返回0,失败返回错误号

线程分离状态

pthread_attr_setdetachstate函数

  • 函数原型:int pthread_attr_setdetachstate(pthread_attr_t *attr, int pthread_state)
  • 参数:对于第二个参数,系统提供了两个宏分别为THREAD_CREATE_DETACHED以及PTHREAD_CREATE_JOINABLE
  • 作用:第一个宏为设置线程为游离态,第二个为加入态
  • 返回值:成功返回0,失败返回错误号

代码演示

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

int main(void) {
    auto f = [](void * arg) mutable -> void* {
        printf("thread start");
        pthread_exit((void*)1);
    };
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    pthread_t tid;
    int ret = pthread_create(&tid, &attr, f, NULL);
    if(ret != 0) {
        printf("create error: %s\n", strerror(ret));
        exit(1);
    }
    ret = pthread_join(tid, NULL);
    sleep(2);
    if(ret != 0) {
        printf("%d\n", ret);
        printf("join error: %s\n", strerror(ret));
    }
    return 0;
}

线程的栈控制

pthread_attr_setstack函数

用来设置线程栈的地址和大小

  • 函数原型:int pthread_attr_setstack(pthread_attr_t * attr, void * stack_addr, size_t stack_size);
  • 函数作用:从堆中申请空间并设置为线程栈
  • 返回值:成功返回0,失败返回错误号

线程属性控制实例

代码演示

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

const int size = 0x10000000;
void * t_fun(void *arg) {
    while(1) {
        sleep(1);
    }
    pthread_exit((void *) 1);
}

int main(void) {
    pthread_attr_t attr;
    pthread_t tid;
    pthread_attr_init(&attr);
    int detached;
    pthread_attr_getdetachstate(&attr, &detached);
    if(detached == PTHREAD_CREATE_DETACHED) {
        puts("thread detached"); 
    }
    if(detached == PTHREAD_CREATE_JOINABLE) {
        puts("thread joinable");
    }
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    void * stackaddr = nullptr;
    int cnt = 0;
    while(1) {
        stackaddr = malloc(size);
        if(stackaddr == nullptr) {
            perror("malloc");
            exit(1);
        }
        pthread_attr_setstack(&attr, stackaddr, size);
        int ret = pthread_create(&tid, &attr, t_fun, NULL);
        if(ret != 0) {
            printf("creat error: %s\n", strerror(ret)); 
            printf("%d threads has create\n", cnt);
            exit(1);
        }
        cnt++;
    }
    pthread_attr_destroy(&attr);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值