Linux线程控制

Linux支持POSIX多线程接口,称为pthread(POSIX Thread的简称),编写Linux下的多线程应用程序,头文件需要包pthread.h

一、线程创建

函数名称pthread_create
函数功能创建一个线程
头文件#include<pthread.h>
函数原型int pthread_create( pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
参数thread:指向线程标识符的指针,用来存储线程标识符
attr:设置线程属性,NULL表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值成功返回0,失败返回出错编号
创建成功时,由thread指向的内存单元被设置为新创建的线程的ID

pthread.h中还声明了一个pthread_self函数,用于获取当前线程自身的ID

函数名称pthread_self
函数功能获取自身线程ID
头文件pthread
函数原型pthread_t pthread_self(void);
参数
返回值>0:返回调用线程的ID
void*thread_run(void*args)
{
    while(1)
    {
        printf("我是新线程[%s],我创建的线程的ID是:%lu\n",(const char*)args,pthread_self());
        sleep(1);
    }
}
int main()
{
    pthread_t tid;
    pthread_create(&tid,NULL,thread_run,"new thread");

    while(1)
    {
        printf("我是主线程,我创建的线程的ID是:%lu\n",tid);
        sleep(1);
    }
    return 0;
}

结果展示:
我们可以看到主线程和新线程的ID是一样的,而且是很大的值。
在这里插入图片描述
我们再来观察一下LWP,为什么和线程ID不一样呢?

注意:pthread并不是Linux系统默认的库,而是POSIX线程库。在Linux中将其作为一个库来使用,因此编译链接时需要加上-lpthread以显式链接该库

如下创建多个线程

//创建多个线程
void *thread_run(void *args)
{
    while (1)
    {
        //printf("我是新线程[%s],我创建的线程的ID是:%lu\n", (const char *)args, pthread_self());
        sleep(3);
    }
}
int main()
{
    pthread_t tid[5];
    int i = 0;
    for (i = 0; i < 5; i++)
    {
        pthread_create(tid + i, NULL, thread_run,(void*)"new thread");
    }
    while (1)
    {
        printf("我是主线程,我的thread ID是:%lu\n", pthread_self());
        printf("##############begin#################\n");
        int i = 0;
        for (i = 0; i < 5; i++)
        {
            printf("我创建的线程[%d]是:%lu\n", i, tid[i]);
        }
        printf("##############end###################\n");
        sleep(1);
    }
    return 0;
}

结果展示:
在这里插入图片描述
线程的健壮性不强

//创建多个线程,其中让3号线程出现野指针
void *thread_run(void *args)
{
    int num=*(int*)args;
    while (1)
    {
        printf("我是新线程[%d],我创建的线程的ID是:%lu\n", num, pthread_self());
        sleep(10);

        // 野指针问题
        if(num==3)
        {
            printf("thread number:%d quit\n",num);
            int *p=NULL;
            *p=100;
        }
    }
}
int main()
{
    pthread_t tid[5];
    int i = 0;
    for (i = 0; i < 5; i++)
    {
        pthread_create(tid + i, NULL, thread_run,(void*)&i);
        sleep(1);
    }
    while (1)
    {
        printf("我是主线程,我的thread ID是:%lu\n", pthread_self());
        printf("##############begin#################\n");
        int i = 0;
        for (i = 0; i < 5; i++)
        {
            printf("我创建的线程[%d]是:%lu\n", i, tid[i]);
        }
        printf("##############end###################\n");
        sleep(1);
    }
    return 0;
}

结果展示:
在这里插入图片描述

二、线程等待

一般而言,线程也是需要被等待的,如果不等待,可能会导致类似于”僵尸进程”的问题!

函数名称pthread_join
函数功能等待线程结束
头文件#include<pthread.h>
函数原型int pthread_join(pthread_t thread, void **retval);
参数thread:线程ID
retval:它指向一个指针,用来存储被等待线程的返回值,
意思就是,这是一个输出型参数,用来获取新线程退出时候,函数的返回值
返回值成功返回0
失败返回错误码
// 线程等待
#define NUM 1
void *thread_run(void *args)
{
    int num=*(int*)args;
    while (1)
    {
        printf("我是新线程[%d],我创建的线程的ID是:%lu\n", num, pthread_self());
        sleep(10);
        break;
    }
    return (void*)111;
}
int main()
{
    pthread_t tid[NUM];
    int i = 0;
    for (i = 0; i < NUM; i++)
    {
        pthread_create(tid + i, NULL, thread_run,(void*)&i);
        sleep(1);
    }
    //指针变量,本身就可以充当某种容器保存数据
    void* status=NULL;
    pthread_join(tid[0],&status);
    printf("ret=%d\n",(int)status);
    return 0;
}

结果展示:
在这里插入图片描述
我们的确实拿到线程退出的返回值。这里的返回值是int,也可以是其他类型的变量,也可以是对象的地址(不能是临时的)。

三、线程终止

线程终止的方案

  • 函数中return(1、main函数中return的时候代表主线程或者进程退出  2、其他线程函数return代表当前线程退出)
  • 新线程通过pthread_ exit终止自己( 而exit是终止进程,不要在其他线程中调用,如果你就想终止一个线程的话! ! )
  • 调用pthread_cancel取消一个线程
函数名称pthread_exit
函数功能结束调用线程
头文件#include<pthread.h>
函数原型void pthread_exit(void *retval);
参数指向退出信息的指针
返回值
// 线程终止
#define NUM 1
void *thread_run(void *args)
{
    int num=*(int*)args;
    while (1)
    {
        printf("我是新线程[%d],我创建的线程的ID是:%lu\n", num, pthread_self());
        sleep(2);
        break;
    }
    pthread_exit((void*)123);
}
int main()
{
    pthread_t tid[NUM];
    int i = 0;
    for (i = 0; i < NUM; i++)
    {
        pthread_create(tid + i, NULL, thread_run,(void*)&i);
        sleep(1);
    }
    //指针变量,本身就可以充当某种容器保存数据
    void* status=NULL;
    // 也要循环式等待线程
    for(i=0;i<NUM;i++)
    {
        pthread_join(tid[0],&status);
    }
    // 这里是int,也可以是其他类型的变量或者对象的地址(不能是临时的)
    printf("ret=%d\n",(int)status);
    sleep(3);
    return 0;
}

结果展示:
我们刚开始主线程+新创建的两个线程同时运行,新线程提前终止,主线程后终止退出,我们能看到明显的现象。
在这里插入图片描述

我们也是可以直接调用exit函数

// 也可以直接调用exit函数
exit(123);
//pthread_exit((void*)123);

在这里插入图片描述

函数名称pthread_cancel
函数功能取消一个线程
头文件#include<pthread.h>
函数原型int pthread_cancel(pthread_t thread);
参数thread:要取消的线程ID
返回值成功返回0
失败返回错误码
// pthread_cancel取消线程
#define NUM 1
void *thread_run(void *args)
{
    int num=*(int*)args;
    while (1)
    {
        printf("我是新线程[%d],我创建的线程的ID是:%lu\n", num, pthread_self());
        sleep(2);
    }
}
int main()
{
    pthread_t tid[NUM];
    int i = 0;
    for (i = 0; i < NUM; i++)
    {
        pthread_create(tid + i, NULL, thread_run,(void*)&i);
        sleep(1);
    }
    printf("wait sub thread...\n");
    sleep(5);

    printf("cancel sub thread..\n");
    pthread_cancel(tid[0]);

    void* status=NULL;
    for(i=0;i<NUM;i++)
    {
        pthread_join(tid[0],&status);
    }
    printf("ret=%d\n",(int)status);
    sleep(3);
    return 0;
}

结果展示:
在这里插入图片描述
其中的退出结果ret=-1代表的是什么呢?
在这里插入图片描述
如果线程被其他线程调用pthread_cancel取消掉,value_ptr所指向的单元里面存放的常数是PTHREAD_CANCELED就是-1

在新线程中取消主线程(实际中不推荐这样写)

// 新线程取消主线程线程
#define NUM 1
pthread_t g_id;// 主线程ID定为全局的
void *thread_run(void *args)
{
    int num=*(int*)args;
    while (1)
    {
        printf("我是新线程[%d],我创建的线程的ID是:%lu\n", num, pthread_self());
        sleep(2);
        pthread_cancel(g_id);// 在新线程中取消主线程
    }
}
int main()
{
    g_id=pthread_self();// 拿到主线程的线程ID
    pthread_t tid[NUM];
    int i = 0;
    for (i = 0; i < NUM; i++)
    {
        pthread_create(tid + i, NULL, thread_run,(void*)&i);
        sleep(1);
    }
    printf("wait sub thread...\n");
    sleep(50);// 50秒内主线程创建新线程,并且不退出

    printf("cancel sub thread..\n");
    pthread_cancel(tid[0]);

    void* status=NULL;
    for(i=0;i<NUM;i++)
    {
        pthread_join(tid[0],&status);
    }
    printf("ret=%d\n",(int)status);
    sleep(3);
    return 0;
}

结果展示:
确实主线程被取消了,但是进程却没有退出,会出现类似于“僵尸进程”的现象
在这里插入图片描述

四、线程分离

上述列举的是几种等待的情况,但是我们不想等待呢?

  • 在任何一个时间点上,线程是可结合的(joinable),或者是分离的(detached)。一个可结合的线程能够被其他线程回收其资源和杀死;在被其他线程回收资源之前,它的存储器资源是不释放的。相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。
  • 线程的分离状态决定一个线程以什么样的方式来终止自己。在默认情况下线程是非分离状态的,在这种情况下,原有的线程等待创建的线程结束。只有当pthread_ join() 函数返回时,创建的线程才算终止,才能释放已占用的系统资源。而分离线程如果没有被其他的线程所等待,当该线程运行结束,线程就终止了,同时也会立刻释放系统资源。线程分离使用pthread_ detach()实现的。
    (可以类比之前信号中所学的signal(SIGCHLD,SIG_IGN))。
函数名称pthread_detach
函数功能使线程处于分离状态
头文件#include<pthread.h>
函数原型int pthread_detach(pthread_t threadID)
参数threadID:要分离的线程
返回值0:成功
错误号:失败
// 线程分离
#define NUM 1
void *thread_run(void *args)
{
    pthread_detach(pthread_self());
    int num=*(int*)args;
    while (1)
    {
        printf("我是新线程[%d],我创建的线程的ID是:%lu\n", num, pthread_self());
        sleep(2);
        break;
    }
    return (void*)(111);
}
int main()
{
    pthread_t tid[NUM];
    int i = 0;
    for (i = 0; i < NUM; i++)
    {
        pthread_create(tid + i, NULL, thread_run,(void*)&i);
        sleep(1);
    }
    printf("wait sub thread...\n");
    sleep(1);
    printf("cancel sub thread..\n");

    void* status=NULL;
    int ret=0;
    for(i=0;i<NUM;i++)
    {
        ret=pthread_join(tid[0],&status);
    }
    printf("ret:%d status:%d\n",ret,(int)status);
    sleep(3);
    return 0;
}

结果展示:
拿到了ret和status
在这里插入图片描述

五、线程ID以及进程地址空间布局

pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。
前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID, 属于NPTL(NATIVE POSIX Thread Library)线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID。

void* thread_run(void*args)
{
     while(1)
    {
        printf("%s id:0x%x\n",(const char*)args,pthread_self());
        sleep(2);
    }
}
int main()
{
    pthread_t tid;
    pthread_create(&tid,NULL,thread_run,(void*)"new thread");
    while(1)
    {
        printf("main thread id:0x%x\n",pthread_self());
        sleep(1);
    }
    return 0;
}

在这里插入图片描述
我们查看到的线程id是pthread库的线程id,不是Linux内核中的LWP,pthread库的线程id是一个内存地址!(虚拟地址)
在这里插入图片描述
在这里插入图片描述
每个线程都要又运行时的临时数据,每个线程都要又自己的私有栈结构!
所以使用了pthread的数据结构:pthread_t,这个结构用来标识线程的ID。

我们如何快速的找到每个特定的线程呢?
我们只需要拿到每个pthread_t id(虚拟地址),那么我们就能在快速的找到这个线程的所有的信息。

用户级线程和内核级线程1:1对应
在这里插入图片描述

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值