【Linux】初识线程

一、什么是线程

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

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

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

        4、区别:在于是否共享地址空间。例如:独居(进程),合租(线程)。

        5、在 Linux  线程是是 CPU 调度和执行的最小单位,进程是系统分配资源和调度的最小单位,可以看成只有一个线程的进


二、Linux 内核线程实现原理

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

         2、从内核里看进程和线程是一样的,都有各自不同的 PCB ,但是 PCB 中指向内存资源的三级页表是相同的。

         3、进程可以蜕变成线程。

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

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

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

三、线程共享资源

       1、文件描述符。

        2、每种信号的处理方式。

        3、当前工作目录。

        4、用户 ID 和组 ID。

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

四、线程非共享资源

        1、线程 id

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

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

        4、errno变量

        5、信号屏蔽字

        6、调度优先级

五、线程优、缺点

       1、优点:

                   (1)、提高程序并发性          

                   (2)、开销小

                   (3)、数据通信、共享数据方便

       2、缺点:

                   (1)、库函数、不稳定

                   (2)、调试、编写困难、gdb 调试不支持

                   (3)、对信号支持不好

六、线程控制原语

       1、pthread_self 函数

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

             函数原型: 

                            

            #include <pthread.h>

                 pthread_t pthread_self(void);

             返回值:

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

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

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

             注意:

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


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


void *thd_func(void *arg)
{

    printf("In thread id = %lu,pid = %u\n",pthread_self(),getpid());
    return NULL;
}


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

    printf("In main 1: id = %lu,pid = %u\n",pthread_self(),getpid());
    ret = pthread_create(&tid,NULL,thd_func,NULL);
    if( ret !=0 )
    {
        fprintf(stderr,"pthread_create error:%s\n",strerror(ret));
        exit(1);
    }
    
    sleep(1);
    printf("In main2: id = %lu,pid = %u\n",pthread_self(),getpid());

    return 0;
}


       2、pthrea_create函数

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

             函数原型:

                            

          #include <pthread.h>

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

             返回值:

                         成功:返回 0 ;失败:返回错误号             --------------------------- 在Linux 下所有和线程相关的控制原语失败均返回 错误号。

             参数:

                     pthread_t:当前Linux 中可以理解为: typedef unsigned long int pthread_t

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

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

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

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

                  在一线程中调用 pthread_create() 创建新的线程后,当线程从 pthread_create() 返回继续往下执行,而新的线程所执行的代码由我们所传给 pthread_create()的函数指针决定。pthread_create 创建成功返回后,新创建的线程 ID 被填写到 thread 参数所指向的内存单元。

                    attr 参数表示线程属性。


           循环创建  N 个线程,示例代码:

                   

/*
 
    循环创建 N 个子进程 
*/


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


void *thd_func(void *arg)
{
    int i = (int)arg;
    sleep(i);
    printf("%dthd id = %lu,pid = %u\n",i+1,pthread_self(),getpid());
    return NULL;
}


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

    return 0;
}
            

           3、线程共享:

                 线程间共享全局变量。

                 注意:线程默认共享数据段、代码段等地址空间,常用的是全局变量。而进程不共享全局变量。只能借助 mmap

           4、pthread_exit 函数

                 作用:将单个线程退出

                 函数原型:


           #include <pthread.h>

                    void pthread_exit(void *retval);
                  参数:

                           retval 表示线程退出状态,通常传 NULL。

                   注意:

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

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

            5、pthread_join 函数

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

                  函数原型:

                        

              #include <pthread.h>

                       int pthread_join(pthread_t thread, void **retval);
                   返回值:

                                成功:返回 0 ; 失败:返回错误号

                    参数:

                                 thread      线程 ID(注意不是指针)

                                 retval        存储线程结束状态。

                     对比进程和线程记忆:

                                 进程中: main 返回值、exit 参数 ------> int;   等待子进程结束   wait  函数参数 ------> int *

                                 线程中: 线程主函数返回值、pthread_exit ---------->void *;等待子线程结束  pthread_join   函数参数 ---------->void **


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


typedef struct
{
    char ch;
    int var;
    char str[64];
}exit_t;

void *thd_func(void *arg)
{
    exit_t *retval = (exit_t*)arg; 
    retval->ch = 'm';
    retval->var = 200;
    strcpy(retval->str,"my Thread\n");

    pthread_exit((void*)retval);
    return NULL;
}


int main(void)
{
    pthread_t tid;
    int ret;
    exit_t *retval = (exit_t*) malloc(sizeof(exit_t));
    ret = pthread_create(&tid,NULL,thd_func,(void*)retval);
    if( ret !=0 )
    {
        fprintf(stderr,"pthread_create error:%s\n",strerror(ret));
        exit(1);
    }
    
    pthread_join(tid,(void**)&retval);
    printf("ch = %c,var = %d,str = %s\n",retval->ch,retval->var,retval->str);
    free(retval);
    return 0;
}

           6、pthread_detach 函数

                作用:实现线程分离

                函数原型:

                           

             #include <pthread.h>

                  int pthread_detach(pthread_t thread);

                  返回值:

                               成功,返回0;失败:返回错误号

                  注意:

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

                           (2)、进程若有该机制,将不会产生僵尸进程。僵尸进程的产生主要由于进程死后,大部分资源被释放,一点残留资源人存留在系统中,导致内核认为该进程仍存在。

                           (3)、也可使用 pthread_create 函数参数 2 (线程属性)来设置线程分离。

                  

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


void *thd_func(void *arg)
{
    int n = 3;
    while(n--)
    {
        printf("thread count = %d\n",n);
        sleep(1);
    }

//  return (void*)1;
    pthread_exit((void*)1);            // return 和 pthread_exit 的作用相同
}


int main(void)
{
    pthread_t tid;
    void *tret;
    int err;
    
    pthread_create(&tid,NULL,thd_func,NULL);

    pthread_detach(tid);        // 让线程分离--------自动退出,无系统残留资源

    while(1)
    {
        err = pthread_join(tid,&tret);
        printf("-------------- err = %d\n",err);
        if( err != 0 )
            fprintf(stderr,"thread_join error: %s\n",strerror(err));
        else
            fprintf(stderr,"thread exit code %d\n",(int)tret);
        sleep(1);
    }

    return 0;
}
 
            7、pthread_cancel 函数

                 作用 ;杀死(取消)线程,其作用对应于进程中的 kill() 函数。

                 函数原型:

                            

               #include <pthread.h>

                       int pthread_cancel(pthread_t thread);

                 返回值:

                             成功:返回 0;失败:返回错误号

                  注意:

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

                           (2)、取消点:是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用 create、open、pause、close、read、write......执行命令 man 7 

threads  可查看具备这些取消点的系统调用列表。

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

                           (4)、被取消的线程,退出定义在 Linux 的 pthread 库中,常数 PTHREAD_CANCEL 的值是 -1 ,可在头文件 pthread.h 中只找到它的定义 #define 

PTHREAD_CANCEL ((void*)-1)。因此,当我们对一个已经被取消的线程使用 pthread_join 回收时,得到的返回值为 -1 。

          

七、线程使用注意事项h

       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、付费专栏及课程。

余额充值