Linux - 线程控制

内核中并没有线程的概念,只有PCB的概念,所以线程并没有系统调用。
但是为了使用户操作线程方便,就有大佬封装了一个POSIX线程库,可以使我们在用户层完成线程的创建销毁、以及其他操作。

POSIX线程库

  • 使用时需要包含 <pthread.h> 头文件
  • 链接这些线程库时,需要加上编译命令 “-lpthread”选项

创建线程

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

参数 
    thread:返回线程ID 
    attr:设置线程的属性,
    attr为NULL表⽰使⽤默认属性 
    start_routine:是个函数地址,线程启动后要执⾏的函数  //线程的入口函数,线程一创建出来就去执行这个函数,主线程的入口函数是main
    arg:传给线程入口函数的参数 

返回值:成功返回0;失败返回错误码

代码演示:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

void* ThreadEntry(void* arg)
{
    (void)arg;
    while(1)
    {
        printf("New Thread\n");
        sleep(1);
    }
    return NULL;
}

int main()
{
    //创建一个线程
    pthread_t tid;
    pthread_create(&tid,NULL,ThreadEntry,NULL);
    //主线程进行死循环
    while(1)
    {
        printf("Main Thread\n");
        sleep(1);
    }
    return 0;
}

线程的执行顺序不一定,由操作系统决定。
利用 pshack [进程id] 显示当前进程中线程的调用炸

LWP 45128 : 作用在用户态,帮助用户控制线程
0x00007fxxxxxx :作用在内核态,协助调度

利用gdb调试线程
使用 gdb attach [进程id]


info thread 查看所有线程

以上 1,2的编号表示现在有线程的编号,*表示当前线程,这时用bt查看调用栈就是1的调用栈

thread [编号] 切换线程


线程终止

  • 从线程 return,如果是从主线程return,相当于exit,所有线程都结束
  • 线程可调用 pthread_exit终止自己
  • 一个线程可以调用 pthread_cancel终止同一进程中的另一进程(参数传 pthread_self()时终止自己)

//线程终止
void pthread_exit(void *value_ptr); 
参数 
    value_ptr:value_ptr不要指向⼀个局部变量,线层入口函数的返回值(基本不用)。

返回值:⽆返回值,跟进程⼀样,线程结束的时候⽆法返回到它的调⽤者(⾃⾝)

//取消一个执行中的线程
int pthread_cancel(pthread_t thread); 
参数 
    thread:线程ID 

返回值:成功返回0;失败返回错误码

线程等待与分离


线程等待 

为什么要线程等待?
  • 线程结束以后会有返回结果(线程入口函数返回的void*),我们需要读取返回结果,回收资源,避免类似于僵尸进程的效果,这里可理解为内存泄漏

进程等待和线程等待
  • 进程等待只可以父进程等子进程,一个线程则可以等待同组的任何一个线程
  • 进程等待(阻塞、非阻塞),线程等待只有阻塞等待

int pthread_join(pthread_t thread, void **value_ptr); 
参数 
    thread:线程ID 
    value_ptr:它指向⼀个指针,后者指向线程的返回值 

一般使用pthread_join来保证线程的结束顺序

返回值:成功返回0;失败返回错误码

代码演示:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

void* ThreadEntry1(void* arg)
{
    (void)arg;
    printf("Thread1\n");
    return (void*)1;
}

void* ThreadEntry2(void* arg)
{
    (void)arg;
    printf("Thread2\n");
    pthread_exit((void*)2);
    return NULL;
}

void* ThreadEntry3(void* arg)
{
    (void)arg;
    //终止自己
    printf("Thread3\n");
    pthread_cancel(pthread_self());
    return NULL;
}

int main()
{
    pthread_t t1,t2,t3;
    pthread_create(&t1,NULL,ThreadEntry1,NULL);
    pthread_create(&t2,NULL,ThreadEntry2,NULL);
    pthread_create(&t3,NULL,ThreadEntry3,NULL);
    void* ret = NULL;
    pthread_join(t1,&ret);//调用该函数的线程会挂起等待,直到t1线程终止
    printf("Thread1 = %p\n",ret);
    pthread_join(t2,&ret);
    printf("Thread2 = %p\n",ret);
    pthread_join(t3,&ret);
    printf("Thread3 = %p\n",ret);
    //3次pthread_join是串行的,必须t1结束以后,对1回收完毕以后才会调用2
    return 0;
}


在学习进程的时候我们也说进程父子进程的执行顺序不一定,但是我们却很难看到子进程先于父进程执行。
那为什么我们在这里很容易可以看到线程执行顺序不一定呢?
其实进程的执行顺序确实是不一定的,但是每次创建一个进程的代价比较大,所以父进程就有很大的概率去执行,而线程的创建代价比较小,所以很容易看到执行顺序不一定这个结果。

但是线层等待的顺序一定是顺序的,可以认为三个等待函数时串行执行的,必须前面的执行完后面的才可以执行。

代码验证线程共享栈、堆全局区的情况

线程分离
  • 在默认情况下创建的线程是joinable的,线程退出后,要对其进行pthread_join操作,否则无法回收资源,造成内存泄漏。
  • 如果不关心线程的返回值,那么join就是一个负担,这个是后可以告诉系统线程退出时,由操作系统自动回收资源。
int pthread_detach(pthread_t tid);

线程也可以对自己进行线程分离
pthread_detach(pthread——self());

joinable和线程分离是两个对立的状态,两者不可能同时存在。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值