线程基础知识

本文详细介绍了Linux环境下的线程概念,包括轻量级进程、线程间资源共享、内核线程实现原理。讨论了线程创建函数pthread_create、获取线程ID的pthread_self、退出线程的pthread_exit以及线程回收的pthread_join等,并提供了相应的代码示例。此外,还提到了线程取消和分离以及线程使用时的注意事项。
摘要由CSDN通过智能技术生成

目录

线程

线程间资源

Linux 内核线程实现原理

线程常用函数

获取线程id pthread_self函数

创建子线程 pthread_create函数

 线程中检查出错返回

练习:循环创建多个子线程

单个线程退出 pthread_exit函数

练习:退出指定子线程

回收线程 pthread_join函数

练习:回收线程并获取子线程返回值

杀死线程 pthread_cancel函数

练习:主线程调用pthread_cancel杀死子线程

分离线程 pthread_detach函数

练习:利用pthread_create中的参数2 attr 设置分离线程

终止线程方式

线程使用注意事项


线程

LWP:轻量级的进程,本质仍是进程(在 Linux 环境下)
进程:独立地址空间,拥有 PCB
线程:有独立的 PCB,但没有独立的地址空间(共享进程的地址空间)
Linux 下: 线程:最小的执行单位
进程:最小分配资源单位,可看成是只有一个线程的进程。

察看线程号: ps –Lf 进程id 查看指定线程的 lwp 号

线程间资源

线程共享资源线程共享资源
文件描述符表线程 id
每种信号的处理方式处理器现场和栈指针(内核栈)
当前工作目录独立的栈空间(用户空间栈)
用户 ID 和组 IDerrno 变量
内存地址空间 (.text/.data/.bss/heap/共享库)信号屏蔽字
全局变量调度优先级

Linux 内核线程实现原理

1. 轻量级进程,也有 PCB,创建线程使用的底层函数和进程一样,都是 clone
2. 从内核里看进程和线程是一样的,都有各自不同的 PCB,但是 PCB 中指向内存资源的三级页表是相同的
3. 进程可以蜕变成线程
4. 线程可看做寄存器和栈的集合
5. 在 linux 下,线程最是小的执行单位;进程是最小的分配资源单位

三级映射:进程 PCB --> 页目录(可看成数组,首地址位于 PCB 中) --> 页表 --> 物理页面 --> 内存单元

对于进程来说,相同的地址(同一个虚拟地址)在不同的进程中,反复使用而不冲突。原因是虽然虚拟地址一样,但是,页目录、页表、物理页面各不相同。相同的虚拟地址,映射到不同的物理页面内存单元,最终访问不同的物理页面。
但是!线程不同!两个线程具有各自独立的 PCB,但共享同一个页目录,也就共享同一个页表和物理页面。所以两个 PCB 共享一个地址空间
实际上,无论是创建进程的 fork,还是创建线程的 pthread_create,底层实现都是调用同一个内核函数 clone。如果复制对方的地址空间,那么就产出一个“进程”;如果共享对方的地址空间,就产生一个“线程”。
因此: Linux 内核是不区分进程和线程的。只在用户层面上进行区分。所以,线程所有操作函数 pthread_* 是库函数,而非系统调用。

线程常用函数

获取线程id pthread_self函数

获取线程id。 线程id是在【进程】地址空间内部,用来标识线程身份的id号,不同进程间线程id可以相同

#include <pthread.h>

pthread_t pthread_self(void);
线程 ID: pthread_t 
类型,本质:在 Linux 下为无符号整数(%lu)

创建子线程 pthread_create函数

#include <pthread.h>

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

参数:
tid:传出参数,表新创建的子线程 id
attr:线程属性。传NULL表使用默认属性。
start_rountn:子线程回调函数。创建成功,ptherad_create函数返回时,该函数会被自动调用。
arg:回调函数的参数。没有的话,传NULL
返回值:
成功:0
失败:errno

 线程中检查出错返回

fprintf(stderr, "xxx error: %s\n", strerror(ret));

练习:循环创建多个子线程

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

int var  = 100;

void *test(void *arg){
    int i = (int)arg;
    var = 200;
    sleep(i);
    printf("I'm %dthread: pid = %d,tid = %lu\n",i+1 ,getpid() ,pthread_self());
    return NULL;
}

int main(int argc,char*argv[]){

    pthread_t thread;

    printf("main: pid = %d,tid = %lu\n",getpid(), pthread_self());
    int i, ret;

    for(i = 0;i<5;i++){
        ret = pthread_create(&thread ,NULL ,test ,(void *)i);
        if(ret != 0){
            fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
            exit(1);
        }
    }
    sleep(5);
    printf("var = %d\n",var);
    return 0;
}

若在pthread_create中传入引用(void *)&i,相应地修改回调函数:int i = *((int *)arg)
子线程里更改全局变量后,主线程里也跟着发生变化

单个线程退出 pthread_exit函数

只退出当前线程

#include <pthread.h>

void pthread_exit(void *retval);

参数 retval:退出值。 无退出值时,NULL

区分	exit();	会导致进程内所有线程全部退出。
		return: 返回到调用者那里去。
		pthread_exit(): 退出当前线程。

练习:退出指定子线程

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

int var = 100;

void *func(void){
    pthread_exit(NULL);
    return NULL;
}
void *test(void *arg){
    int i = (int)arg;
    var = 200;
    sleep(i);
    if(i == 2){
        //exit(0);//退出进程
        //return NULL;//表示返回到调用者哪里去
        func();
    }
    printf("I'm %dthread: pid = %d,tid = %lu\n",i+1 ,getpid() ,pthread_self());
    return NULL;
}

int main(int argc,char*argv[]){

    pthread_t thread;
    printf("main: pid = %d,tid = %lu\n",getpid(), pthread_self());
    int i, ret;

    for(i = 0;i<5;i++){
        ret = pthread_create(&thread ,NULL ,test ,(void *)i);
        if(ret != 0){
            fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
            exit(1);
        }
    }
    printf("var = %d\n",var);

    pthread_exit(NULL);
}

回收线程 pthread_join函数

阻塞回收线程

#include <pthread.h>

int pthread_join(pthread_t thread, void **retval);
参数    thread: 待回收的线程id
		retval:传出参数。 回收的那个线程的退出值。
			    线程异常终止,值为 PTHREAD_CANCELED。
返回值:成功:0	
       失败:errno

练习:回收线程并获取子线程返回值

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

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

void *tfn(void *arg){
    struct thrd *tval;

    tval = malloc(sizeof(tval));

    tval->val = 100;
    strcpy(tval->str,"hello thread");

    return (void *)tval;
}

int main(int argc,char* argv[]){

    pthread_t tid;
    struct thrd *retval;

    int ret = pthread_create(&tid, NULL, tfn, NULL);
    if(ret < 0){
        fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
        exit(1);
    }

    int jret = pthread_join(tid, (void **)&retval);
    if(jret != 0){
        fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
        exit(1);
    }

    printf("child thread var :%d, str:%s\n", retval->val, retval->str);

    pthread_exit(NULL);
}

杀死线程 pthread_cancel函数

杀死一个线程。 需要到达取消点(保存点)

#include <pthread.h>

int pthread_cancel(pthread_t thread);
参数:   thread: 待杀死的线程id
返回值: 成功:0
		失败:errno

如果,子线程没有到达取消点, 那么 pthread_cancel 无效。
我们可以在程序中,手动添加一个取消点。使用 pthread_testcancel();
成功被 pthread_cancel() 杀死的线程,返回 -1.使用pthead_join 回收。

练习:主线程调用pthread_cancel杀死子线程

void *tfn1(void* arg){
    printf("thread 1 returning\n");
    return (void *)111;
}

void *tfn2(void* arg){
    printf("thread 2 returning\n");
    return (void *)222;
}

void *tfn3(void* arg){
    while(1){
        printf("thread 3 I am going to die in 3 seconds ...\n");
        sleep(1);

        pthread_testcancel();//自己添加取消点
    }

    return (void *)666;
}

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

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

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

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

    pthread_exit(NULL);
}

分离线程 pthread_detach函数

线程分离状态:指定该状态,线程主动与主控线程断开关系。线程结束后,其退出状态不由其他线程获取,而直接自己自动释放。网络、多线程服务器常用。

设置线程分离,线程终止时,会自动清理PCB,无需回收

#include <pthread.h>

int pthread_detach(pthread_t thread);

参数:  thread: 待分离的线程id
返回值:	成功:0
		失败:errno

练习:利用pthread_create中的参数2 attr 设置分离线程

设置分离属性。
pthread_attr_t attr;

创建一个线程属性结构体变量
pthread_attr_init(&attr);

初始化线程属性
pthread_attr_setdetachstate(&attr,  PTHREAD_CREATE_DETACHED);
设置线程属性为 【分离态】

pthread_create(&tid, &attr, tfn, NULL);
借助修改后的 设置线程属性 创建为分离态的新线程

pthread_attr_destroy(&attr);
销毁线程属性

终止线程方式

终止某个线程而不终止整个进程,有三种方法:
1. 从线程主函数 return。这种方法对主控线程不适用,从 main 函数 return 相当于调用 exit。
2. 一个线程可以调用 pthread_cancel 终止同一进程中的另一个线程。
3. 线程可以调用 pthread_exit 终止自己。

线程使用注意事项

1. 主线程退出其他线程不退出,主线程应调用 pthread_exit
2. 避免僵尸线程
pthread_join
pthread_detach
pthread_create 指定分离属性
被 join 线程可能在 join 函数返回前就释放完自己的所有内存资源,所以不应当返回被回收线程栈中的值;
3. malloc 和 mmap 申请的内存可以被其他线程释放(线程间共享)
4. 应避免在多线程模型中调用 fork 除非,马上 exec,子进程中只有调用 fork 的线程存在,其他线程在子进程中均 pthread_exit
5. 信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值