栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

Linux学习日记19——线程

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Linux学习日记19——线程

学习视频链接 

黑马程序员-Linux系统编程_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1KE411q7ee?p=151&spm_id_from=333.1007.top_right_bar_window_history.content.click

目录

一、线程概念

1.1 什么是线程

1.2 查看火狐浏览器的线程

1.3 Linux内核线程实现原理

1.4 线程共享资源

1.5 线程非共享资源

1.6 线程优、缺点

二、进程控制原语

2.1 pthread_self 函数

2.2 pthread_create 函数

2.3 测试代码


一、线程概念

1.1 什么是线程

LWP:light weight process 轻量级的进程,本质仍是进程(在 Linux 环境下)

进程:独立地址空间,拥有 PCB

线程:有独立的 PCB,但没有独立的地址空间(共享)

区别:在于是否共享地址空间 独居(进程);合租(线程)

Linux下:

线程:最小的执行单位

进程:最小分配资源单位,可看成是只有一个线程的进程

进程创建线程,每个线程都有 PCB,原来的进程变成线程了

1.2 查看火狐浏览器的线程

 进程 id 是同一个,进程号有多少,共有 22 个

1.3 Linux内核线程实现原理

类 Unix 系统中,早期是没有“线程”概念的,80 年代才引入(应减少信号和线程混用),借助进程机制实现出了线程的概念。因此在这类系统中,进程和线程关系密切

1、轻量级进程(light-weight process),也有 PCB,创建线程使用的底层函数和进程一样,都是 clone

2、从内核里看进程和线程是一样的,都有各自不同的 PCB,但是 PCB 中指向内存资源的三级页表(参考操作系统分页式存储)是相同的

3、进程可以蜕变成线程

4、线程可看做寄存器和栈的集合

5、在 linux 下,线程最是小的执行单位;进程是最小的分配资源单位

察看 LWP 号:ps -Lf pid 查看指定线程的 lwp 号

1.4 线程共享资源

1、文件描述符表

2、每种信号的处理方式

3、当前工作目录

4、用户 ID 和组 ID

5、内存地址空间(.text/ .data/ .bss/heap/共享库)(不贡献栈)

1.5 线程非共享资源

1、线程 ID

2、处理器现场和栈指针(内核栈)

3、独立的栈空间(用户空间栈)

4、errno 变量

5、信号屏蔽字

6、调度优先级

1.6 线程优、缺点

1、优点

提高程序并发性,开销小,数据通信、共享数据方便

2、缺点

库函数、不稳定,调试、编写困难、GDB不支持,对信号支持不好

优点相对凸出,缺点不是硬伤。Linux下由于实现方法导致进程、线程差别不是很大

二、进程控制原语

2.1 pthread_self 函数

1、作用

获取线程 ID。其作用对应进程中 getpid() 函数

2、pthread_t pthread self(void);

返回值:成功:0;失败:无

3、线程 ID

pthread_t 类型,本质:在Linux下为无符号整数 (%lu),其他系统中可能是结构体实现

线程 ID 是进程内部 识别标志。(两个进程间,线程 ID 允许相同)

4、注意

不应使用全局变量 pthread_t tid;  在子线程中通过 pthread_create 传出参数来获取线程 ID,而使用 pthread_self

2.2 pthread_create 函数

1、作用

创建一个新线程    其作用,对应进程中 fork() 函数

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

返回值:成功:0;   失败:错误号  —— Linux 环境下,所有线程特点,失败均直接返回错误号

参数:

参数1:传出参数,保存系统为我们分配好的线程 ID

参数2:通常传 NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数

参数3:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束

参数4:线程主函数执行期间所使用的参数

2.3 测试代码

1、查看父进程的线程 id

2、创建线程,主线程执行 main 函数,创建的线程执行 tfn

3、循环创建多个子线程

4、错误写法

#include 
#include 
#include 
#include 
#include 
#include 

void sys_err(const char *str)
{
    perror(str);
    exit(1);
}


void *tfn(void *arg)
{
    int i = *((int *)arg);
    sleep(i);
    printf("I'm  %dth thread: pid = %d, tid = %lun", i + 1, getpid(), pthread_self());
    return NULL;
}

int main(int argc, char *argv[]) 
{
    int i;
    int ret;
    pthread_t tid;

    for (int i = 0; i < 5; i++) {
        ret = pthread_create(&tid, NULL, tfn, (void*)&i);
        if (ret != 0) {
            perror("pthread_create error");
        }   
    }   

    sleep(5);
    printf("I'm main thread: pid = %d, tid = %lun", getpid(), pthread_self());

    return 0;
}

原因:地址传递和值传递的效果不一样,线程传参要用值传递而不是地址传递。其中要借助两次强制类型转换 int(4字节) —> char(8字节) —> int(4字节)

5、线程间全局变量共享

#include 
#include 
#include 
#include 
#include 
#include 

int var = 100;

void sys_err(const char *str)
{
    perror(str);
    exit(1);
}

void *tfn(void *arg)
{
    var = 200;
    printf("In thread, change var = %dn", var);

    return NULL;
}

int main(void) 
{
    printf("At first var = %dn", var);

    pthread_t tid;
    pthread_create(&tid, NULL, tfn, NULL);
    sleep(1);

    printf("After pthread_create var = %dn", var);

    return 0;
}

2.4 pthread_exit

1、作用

将单个线程退出

2、以前代码存在的问题

(1) 我们想退出某个线程,使用 exit 会退出整个进程

#include 
#include 
#include 
#include 
#include 
#include 

void sys_err(const char *str)
{
    perror(str);
    exit(1);
}

void *tfn(void *arg)
{
    int i =(int)arg;
    sleep(i);
        
    if (i == 2) {
        exit(0);
    }   
    printf("I'm %dth thread: pid = %d, tid = %lun", i + 1, getpid(), pthread_self());

    return NULL;
}

int main(void) 
{
    int i;
    int ret;
    pthread_t tid;

    for (i = 0; i < 5; i++) {
        ret = pthread_create(&tid, NULL, tfn, (void *)i);
        if (ret != 0) {
            sys_err("pthread_create error");
        }   
    }   

    sleep(5);

    return 0;
}

(2) return 能达到退出线程的目的 

但是在函数调用内使用 return 是达不到这样的效果的

 

3、void pthread_exit(void *retval);

参数:retval 表示线程退出状态,通常传 NULL

在不添加 sleep 控制输出顺序的情况下。pthread_create 在循环中,几乎瞬间创建 5 个线程,但只有第 1 个线程有机会输出(或者第 2 个也有,也可能没有,取决于内核调度)如果第 3 个线程执行了 exit,将整个进程退出了,所以全部线程退出了

所以,多线程环境中,应尽量少用,或者不使用 exit 函数,取而代之使用 pthread_exit 函数,将单个线程退出。任何线程里 exit 导致进程退出,其他线程未工作结束,主控线程退出时不能 return 或 exit

另注意,pthread_exit 或者 return 返回的指针所指向的内存单元必须是全局的或者是用 malloc 分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了

4、代码

现在主线程不用睡眠了,只退出主线程就可以了,子线程不受影响

 

5、总结

在主函数里面执行 return 和 exit 效果是一样的

return:回到调用者那里去

exit:退出当前进程

pthread_exit():退出当前进程

2.5 pthread_join函数

1、作用

阻塞等待线程退出,获取线程退出状态其作用, 对应进程中 waitpid() 函数

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

成功:0;失败:错误号

参数:thread:线程 ID([注意]:不是指针);retval:存储线程结束状态

和 wait 类比,wait 退出值是 int 所以传出参数是 int *,线程返回值是 void * 所以传出参数是 void **

3、代码

#include 
#include 
#include 
#include 
#include 
#include 

struct thrd {
    int  var;
    char str[256];
};

void sys_err(const char *str)
{
    perror(str);
    exit(1);
}

void *tfn(void *arg)
{
    struct thrd *tval;
    tval = malloc(sizeof(tval));
    tval->var = 100;
    strcpy(tval->str, "hello thread");

    return (void*)tval;
}

int main(void) 
{
    pthread_t tid;
    struct thrd *retval;

    int ret = pthread_create(&tid, NULL, tfn, NULL);
    if (ret != 0) {
        sys_err("pthread_create error");
    }

    ret = pthread_join(tid, (void **)&retval);
    if (ret != 0) {
        sys_err("phread_join error");
    }

    printf("child thread exit with var = %d, str = %sn", retval->var, retval->str);

    pthread_exit(NULL);  // 将当前线程退出
}

 

2.6 pthread_cancel函数

1、作用

杀死(取消)线程。        其作用,对应进程中 kil() 函数

2、int pthread_cancel(pthread_t thread);

成功:0;失败:错误号

[注意] 线程的取消并不是实时的,而有一定的延时。需要等待线程到达某个取消点(检查点)

类似于玩游戏存档,必须到达指定的场所(存档点,如:客栈、仓库、城里等)才能存储进度。杀死线程也不是立刻就能完成,必须要到达取消点

取消点:是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用 creat,open,pause,close,read,write .... 执行命令 man 7 pthreads 可以查看具备这些取消点的系统调用列表。也可参阅 APUE.12.7 取消选项小节

可粗略认为一个系统调用(进入内核)即为一个取消点。如线程中没有取消点,可以通过调pthread_testcancel 函数自行设置一个取消点

被取消的线程,退出值定义在 Linux 的 pthread 库中。常数 PTHREAD_CANCELED 的值是 -1 可在头文件 pthread.h 中找到它的定义:#define PTHREAD_CANCELED(void *) -1)。因此当我们对一个已经被取消的线程使用 pthread_join 回收时,得到的返回值为 -1

3、代码

(1) 杀死子进程演示

#include 
#include 
#include 
#include 
#include 
#include 

void sys_err(const char *str)
{
    perror(str);
    exit(1);
}

void *tfn(void *arg)
{
    while (1) {
        printf("thread: pid = %d, tid = %lun", getpid(), pthread_self());
        sleep(1);
    }
    
    return NULL;
}

int main(void) 
{
    pthread_t tid;

    int ret = pthread_create(&tid, NULL, tfn, NULL);
    if (ret != 0) {
        sys_err("pthread_create error");
    }

    printf("main: pid = %d, tid = %lun", getpid(), pthread_self());

    sleep(5);    

    ret = pthread_cancel(tid);
    if (ret != 0) {
        sys_err("phread_join error");
    }

    while(1);

    pthread_exit(NULL);
}

 

(2) 测试被杀死的子进程的返回值

#include 
#include 
#include 
#include 
#include 
#include 

void sys_err(const char *str)
{
    perror(str);
    exit(1);
}

void *tfn1(void *arg)
{
    printf("thread 1 returningn");

    return (void *)111;
}

void *tfn2(void *arg)
{
    printf("thread 2 exitingn");

    pthread_exit((void *)222);
}

void *tfn3(void *arg)
{
    while(1) {
        printf("thread 3 : I'm going to die in 3 seconds ...n");
        sleep(1);
    }   
    return (void *)111;
}


int main(void)
{
    pthread_t tid;
    void *tret = NULL;

    pthread_create(&tid, NULL, tfn1, NULL);
    pthread_join(tid, &tret);
    printf("thread 1 exit code = %dnn", (int)tret);

    pthread_create(&tid, NULL, tfn2, NULL);
    pthread_join(tid, &tret);
    printf("thread 2 exit code = %dnn", (int)tret);

    pthread_create(&tid, NULL, tfn3, NULL);
    sleep(3);
    pthread_cancel(tid);
    pthread_join(tid, &tret);
    printf("thread 3 exit code = %dn", (int)tret);

    return 0;
}

 

(3) 杀死一个进程需要一个契机(进入内核)

现在处于死循环进入不了内核

 需要有系统调用或者手动添加一个取消点

 

 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/837072.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号