Linux线程(1)--基本概念以及基本函数

1 线程的基本概念

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

  • 进程:拥有独立的地址空间,拥有PCB,相当于独居。

  • 线程:有PCB,但没有独立的地址空间,多个线程共享进程空间,相当于合租。

  • 在 Linux操作系统下:

    • 线程:最小的执行单位
    • 进程:最小分配资源单位,可以看成只有一个线程的进程。

  • 线程的特点:

    • 类Unix系统中,早期没有“线程”的概念,80年代才引入,借助进程机制实现出线程的概念。因此在这类系统中,进程和线程关系密切。
    • 线程是轻量级进程(light weight process),也有PCB,创建线程使用的底层函数和进程一样,都是clone
    • 从内核看进程和线程是一样的,都有各自不同的PCB
    • 进程可以蜕变成线程
    • 在 linux 下,线程是最小的执行单位;进程是最小的分配资源单位

在这里插入图片描述

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

实际上,无论是创建进程的fork,还是创建线程的pthread_create,底层实现都是调用同一个内存函数

  • 如果复制对方的地址空间,那么就产出一个“进程”
  • 如果共享对方的地址空间,就产生一个“线程”。

因此,Linux 内核是不区分进程和线程的,只在用户层面上进行区分。

所有,线程所有操作函数 pthread_* 是库函数,而非系统调用



2 线程共享资源

  • 文件描述符表
  • 每种信号的处理方式
  • 当前工作目录
  • 用户ID和组ID
  • 内存地址空间(.text / .data / .bss / heap / 共享库)


3 线程非共享资源

  • 线程id
  • 处理器现场和栈指针(内核栈)
  • 独立的栈空间(用户空间栈)
  • errno 变量
  • 信号屏蔽字
  • 调度优先级


4 线程优、缺点

  • 优点:

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

  • 缺点:

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

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



5 pthread_create函数–创建子线程

#include <pthread.h>

/*
 * function: 创建一个线程
 * 
 * function arguments:
 *  argv1: 传出参数,保存系统分配好的线程ID
 *  argv2: 通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数
 *  argv3: 函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束
 *  argv4: 线程主函数指向期间所使用的参数。
 *
 * return value:
 *  success: 返回0
 *  faild: 返回错误号
 */

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

/*
 * 注意点:
 * 1.由于pthread_create的错误码不保存在errno中,因此不能直接使用perror()打印错误信息,
 *   可以先用strerror()把错误码转换成错误信息再打印。
 * 2.如果任意一个线程调用了exit或_exit,则整个进程的所有线程都终止
 *  (在main函数调用return也相当于调用了exit)
 */

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

struct Person
{
    char name[64];
    int  age;
};

void *MyThread(void *arg)
{
	
	//struct Person *stPersonPtr = (struct Person*)arg;
	// 此处尽量不要用指针接收,而应该接收传入的值,因为传入的是主线程的栈地址(1.生存周期有限,2.之后可能被主线程修改此这块内存空间的值,子线程会受影响)
    struct Person stPerson = *(struct Person*)arg;
    
    printf("姓名:%s 年龄:%d\n", stPerson.name, stPerson.age);
    printf("child thread, pid = %d, threadId = %d\n", getpid(), pthread_self());
}                                                 
                                            
int main(int argc, char *argv[])            
{                                           
    struct Person stPerson{};               
    strcpy(stPerson.name, "小明");
    stPerson.age = 18;                      
                               
    pthread_t threadID;        
  
  	// 创建子线程                             
    int ret = pthread_create(&threadID, NULL, MyThread, (void*)&stPerson);
    if (ret != 0)              
    {                          
        printf("create thread faild: %s\n", strerror(ret));
        return -1;             
    }                          
                               
    printf("main thread, pid = %d, threadId = %d\n", getpid(), pthread_self());
                             
    sleep(1);   // 为了让子线程执行完成                          
    return 0;   
                  
}                              


6 pthread_exit函数–退出线程

#include <pthread.h>

/*
 * function: 将单个线程退出
 *
 * function arguments:
 *  argv1: 表示线程的退出状态,通常传NULL
 */

void pthread_exit(void *retval);

/*
 * 注意点:
 *  pthread_exit或者return返回的指针所指向的内存单元必须是全局变量或者堆内存变量,
 *  不能在线程函数的栈分配,因为当其他线程得到这个返回指针时,线程函数已经退出了
 *  栈空间就会被回收。
 */


7 pthread_join函数–回收线程

#include <pthread.h>

/*
 * function: 阻塞等待线程退出,获取线程退出状态。其作用,对应进程中的waitpid()函数
 *
 * function arguments:
 *  argv1: 等待退出的线程ID
 *  argv2: 存储线程结束状态,整个指针和pthread_exit函数的参数是同一块地址
 *
 * return value:
 *  success: 返回0
 *  faild: 返回错误号
 */

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

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

struct Person
{
    char name[64];
    int  age;
};

int g_val = 99;

void *MyThread(void *arg)
{

    //struct Person *stPersonPtr = (struct Person*)arg;
    // 此处尽量不要用指针接收,而应该接收传入的值,因为传入的是主线程的栈地址(1.生存周期有限,2.之后可能被主线程修改此这块内存空间的值,子线程会受影响)
    struct Person stPerson = *(struct Person*)arg;

    printf("姓名:%s 年龄:%d\n", stPerson.name, stPerson.age);
    printf("child thread, pid = %d, threadId = %d\n", getpid(), pthread_self());

    pthread_exit(&g_val);
}

int main(int argc, char *argv[])
{
    struct Person stPerson{};
    strcpy(stPerson.name, "小明");
    stPerson.age = 18;

    pthread_t threadID;

    // 创建子线程                             
    int ret = pthread_create(&threadID, NULL, MyThread, &stPerson);
    if (ret != 0)
    {
        printf("create thread faild: %s\n", strerror(ret));
        return -1;
    }

    printf("main thread, pid = %d, threadId = %d\n", getpid(), pthread_self());

    // 回收子线程,并获取线程退出状态
    void *p = NULL;
    pthread_join(threadID, &p);

    int *pInt = (int*)p;

    printf("status: %d\n", *pInt);

    return 0;
}


8 pthread_detach函数–实现线程分离

#include <pthread.h>

/*
 * function: 实现线程分离
 *
 * function arguments:
 *  argv1: 需要分离的线程的线程号
 *
 * return value:
 *  success: 返回0
 *  faild: 返回错误号
 */

int pthread_detach(pthread_t thread);

/*
 * 一情况下,线程终止后,其终止状态保留到其他线程调用pthread_join获取它的终止状态为止。
 * 但是线程也可以设置detcah状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不是保留其终止状态。
 * 不能对一个处于detach状态的线程调用pthread_join,就算调用了pthread_join也将返回错误。也就是说,
 * 如果对一个线程调用了pthread_detach就不能再调用pthread_join了。
 */

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

struct Person
{
    char name[64];
    int  age;
};

void *MyThread(void *arg)
{
     
    //struct Person *stPersonPtr = (struct Person*)arg;
    // 此处尽量不要用指针接收,而应该接收传入的值,因为传入的是主线程的栈地址(1.生存周期有限,2.之后可能被主线程修改此这块内存空间的值,子线程会受影响)
    struct Person stPerson = *(struct Person*)arg;
        
    printf("姓名:%s 年龄:%d\n", stPerson.name, stPerson.age);
    printf("child thread, pid = %d, threadID = %d\n", getpid(), pthread_self());

    pthread_exit(NULL);
}

int main(int argc, char *argv[])    
{                                   
    struct Person stPerson{};       
    strcpy(stPerson.name, "小明");
    stPerson.age = 18;                
                                      
    pthread_t threadID;    
  
    // 创建子线程                             
    int ret = pthread_create(&threadID, NULL, MyThread, &stPerson);
    if (ret != 0)                  
    {                              
        printf("create thread faild: %s\n", strerror(ret));
        return -1;                 
    }                              
                                   
    printf("main thread, pid = %d, threadID = %d\n", getpid(), pthread_self());
                                   
    // 设置线程分离
    pthread_detach(threadID);

    ret = pthread_join(threadID, NULL);
    if (ret != 0)
    {
        printf("pthread_join faild:%s\n", strerror(ret));
    }

    return  0;
}


9 pthread_cancel函数–取消线程

#include <pthread.h>

/*
 * function: 杀死(取消)线程。其作用,对应进程中的kill()函数
 *
 * function arguments:
 *  argv1: 需要取消的线程的线程号
 *
 * return value:
 *  success: 返回0
 *  faild: 返回错误号
 */

int pthread_cancel(pthread_t thread);

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

// 取消点: 是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用creat,open
//         pause,close,read,write...(可以粗略认为会进入内核即为一个取消点)  

//         执行 man 7 pthreads可以查看具备这些取消点的系统调用总列表

//         或者通过pthread_testcancel函数设置一个取消点


10 pthread_testcancel函数–设置取消点

#include <pthread.h>

/*
 * function: 设置取消点
 */

void pthread_testcancel(void);

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

void *MyTread(void *argv)
{
    while (1) 
    {   
        int a;
        int b;

        // 设置取消点
        pthread_testcancel();

    }   
}

int main()
{
    pthread_t threadID;
    int ret = pthread_create(&threadID, NULL, MyTread, NULL);
    if (ret != 0)
    {   
        printf("pthread_create faild:%s\n", strerror(ret));
    }   

    // 取消线程
    ret = pthread_cancel(threadID);
    if (ret != 0)
    {   
        printf("pthread_cancel faild:%s\n", strerror(ret));
    }   

    sleep(10);

    return 0;
}


11 进程函数和线程函数比较

进程线程
forkpthread_create
exitpthread_exit
wait/waitpidpthread_join
killpthread_cancel
getpidpthread_self
-pthread_detach

12 线程属性

根据pthread_create函数的第二个参数可以设置线程属性(分离/非分离)。

线程的分离状态决定一个线程以什么样的方式来终止自己,有两种状态:

  1. 非分离状态:线程的默认属性是非分离状态,这个情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。
  2. 分离属性:分离线程没有被其他的线程等待回收,自己运行结束了,线程也就终止了,马上释放系统资源。

默认线程属性是非分离的,可以通过以下步骤将线程设置为分离属性

  • 第一步:定义线程数据类型变量
    • pthread_arrt_t arrt;
  • 第二步: 对线程属性变量进行初始化
    • int pthread_attr_init(pthread_attr_t* attr);
  • 第三步:设置线程分离属性
    • int pthread_attr_setdetachstate(pthread_attr_t* attr, int detachstate)
      • 参数:
      • attr: 线程属性
      • detachstates:
        • PTHREAD_CREATE_DETACHED(分离)
        • PTHREAD_CREATE_OINABLE(非分离)
      • 注意:这一步完成之后调用pthread_create创建线程,将attr传入,创建的线程就是分离属性
  • 第四步:释放线程属性资源
    • int pthread_attr_destroy(pthread_attr_t* attr);
      • 参数:线程属性

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

void *MyThread(void *argv)
{
    printf("I'm child tread\n");
    pthread_exit(NULL);
}

int main()
{
    // 1.创建分离属性变量
    pthread_attr_t attr;

    // 2.对分离属性变量进行初始化
    pthread_attr_init(&attr);

    // 3.设置线程分离属性
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

    pthread_t threadID;
    int ret = pthread_create(&threadID, &attr, MyThread, NULL); // 创建子线程(第二个参数为线程属性)
    if (ret != 0)
    {   
        printf("pthread_create faild:%s\n", strerror(ret));
        return -1; 
    }   

    // 4.释放线程属性资源
    pthread_attr_destroy(&attr);

    // 验证线程是否为分离属性
    ret = pthread_join(threadID, NULL);
    if (ret != 0)
    {   
        printf("pthread_join faild:%s, 线程是分离属性\n", strerror(ret));
    }   

    sleep(1);

    return 0;
}

13 线程使用注意事项

  1. 主线程退出其他线程不退出,主线程应调用pthread_exit
  2. 避免僵尸线程(pthread_join、pthread_detach、pthread_create指定分离属性)
  3. malloc 和 mmap申请的内存可以被其他线程释放
  4. 应该避免在多线程模型中调用fork,除非马上exec。(子进程中只有调用fork的线程存在,其他线程在子进程中均pthread_exit)
  5. 信号的复杂语义很难和多线程共存,应该避免在多线程引入信号机制
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值