Linux线程控制

24 篇文章 1 订阅

🧸🧸🧸各位大佬大家好,我是猪皮兄弟🧸🧸🧸
在这里插入图片描述

一、 线程创建pthread_create

在这里插入图片描述
参数一:线程id(由于在pthread原生线程库中要维护线程id,线程临时存储,线程栈等数据,那么干脆直接用这块空间的起始地址作为线程id)
参数二:线程属性,我们一般不关心,设为nullptr
参数三:函数指针,回调函数
参数四:给参数三的参数,完成回调

void* threadRoutine(void*args)
{
    while(true)
    {
        cout << "新线程: " << (char*)args <<" running ..."<<endl;
        sleep(1);
        int a = 100;
        a/=0;//除零错误,发送信号,线程异常=进程异常,进程退出
    }
}
int main()
{
    pthread_t tid;
    //Routine例程
    int n =  pthread_create(&tid,nullptr,threadRoutine,(void*)"thread 1");//线程id地址,线程属性,回调方法,回调方法参数
    assert(n==0);
    (void)n;
    while(true)
    {
        cout << "main线程: " << " running ..." << endl;
        sleep(1);
    }

    return 0;
}

在这里插入图片描述
除零异常,CPU内状态寄存器中标记位被设置,每次切换上去都是异常,所以由硬件引发的异常,发送信号(如果这个信号被自定义捕捉,不退出,则进入死循环)

得出结论:
1.线程谁先运行与调度器相关
2.线程一旦异常,都可能导致整个进程整体退出
3.线程在创建并执行的时候,线程也是需要等待的,如果主线程不等待,会引起类似进程的僵尸问题,进而导致内存泄漏

先后才能创建的回调函数的返回值问题:
如果我想在这个回调函数执行完后返回一个特定的值,就可以return
这个返回值谁等我就给谁,一般是主线程在等,那么主线程如何去获取呢?
当然主线程创建出新线程办事,如果必要的话,需要告诉我这个事办的怎么样,用pthread_join的第二个参数retval来获取

二、线程等待pthread_join

join(关联,等待)
在这里插入图片描述
在这里插入图片描述
成功返回 0,失败返回一个错误退出码
等待一个终止的线程
参数一:线程id
参数二:,pthread_join的接收retval就是void**,这是因为传一个void*的参数过去接收,那么就相当于是整个参数取地址(这是因为信息是要让主线程知道,所以是主线程中创建一个变量来接收)

void* threadRoutine(void*args)
{
    int i=0;
    while(true)
    {
        cout << "新线程: " << (char*)args <<" running ..."<<endl;
        sleep(1);
        if(i++ == 10)
        {
            break;
        }
    }
    cout << "new thread quit ..." << endl;
    return nullptr;
}
int main()
{
    pthread_t tid;
    //Routine例程
    void* ret=nullptr;
    pthread_create(&tid,nullptr,threadRoutine,(void*)"thread 1");//线程id地址,线程属性,回调方法,回调方法参数
    pthread_join(tid,&ret);//线程id,等待tid线程
    //在这里一直阻塞等待新线程退出,加了join,我就一定能知道谁先退出
    cout<<"main thread wait done ... main quit ...:new thread qiut : "<< (int)ret <<"\n";
    return 0;
}
//ret就是pthread_join等待出来的信息

在这里插入图片描述

多进程因为独立性,子进程退出后就只能看到别人的退出码,核心转储,信号(通过status看到)
而多线程的内容就丰富了,通过void*的类型来接收所有类型,那么就包括结构体,那么其中就可以包含各种各样的信息

线程异常退出的理解

线程异常,就相当于进程出现异常,因为线程间大部分资源共享,那么我出现一个问题,那么状态寄存器修改,所有进程就都知道了,都会异常退出(状态寄存器异常,给进程发送信号)

三、线程终止

1.在线程的回调函数中return即可
2.exit(),这个是进程退出,一退出所以线程就都退出了,小心使用
3.线程终止pthread_exit()
在这里插入图片描述

pthread_exit(void*retval);
//这是pthread原生线程库提供的方法
//因为pthread_create的返回类型是void*,这才导致pthread_create需要带参数void*
pthread_exit((void*)13);//像这样使用即可

4.pthread_cancel线程取消
在这里插入图片描述
通过传递线程id来取消线程,相当于给目标线程发送一个取消请求

void* threadRoutine(void*args)
{
    while(true)//死循环
    {
        cout << "新线程: " << (char*)args <<" running ..."<<endl;
        sleep(1);
    }
    cout << "new thread quit ..." << endl;
}
int main()
{
    pthread_t tid;
    //Routine例程
    pthread_create(&tid,nullptr,threadRoutine,(void*)"thread 1");//线程id地址,线程属性,回调方法,回调方法参数
    //主线程做自己的事
    int count=0;
    while(true)
    {
        cout<<"main线程: "<<"running ..."<<endl;
        sleep(1);
        count++;
        if(count>=5) break;
    }

    pthread_cancel(tid);//tid线程取消
    cout<<"pthread_cancel: "<<tid<<endl;

    int*ret = nullptr;
    pthread_join(tid,(void**)&ret);//线程id,等待tid线程
    cout<<"main thread wait done ... main quit ...:new thread qiut : "<<(int)ret<<"\n";
    //(int)ret会精度丢失,编译需要加选项
 }

不同的线程退出方案需要在不同的应用场景下使用

三个细节

细节一:线程一旦被取消,join的时候,退出码是-1
也就是说pthread_cancel会返回-1让我们等待的时候接收到
-1其实是宏PTHREAD_CALCELD代表已经被取消

#define PTHREAD_CANCELD ((void*)-1)

细节二:pthread_cancel的使用场景是线程跑了一段时间,觉得跑的差不多了,不需要它了就取消

void* threadRoutine(void*args)
{
    int i=0;
    int*data = new int[10];//申请十个元素
    while(true)
    {
        cout << "新线程: " << (char*)args <<" running ..."<<endl;
        sleep(1);
    exit(10);
    cout << "new thread quit ..." << endl;
    return (void*)data;//给主线程返回计算结果
}
int main()
{
    pthread_t tid;
    //Routine例程
    pthread_create(&tid,nullptr,threadRoutine,(void*)"thread 1");//线程id地址,线程属性,回调方法,回调方法参数
    pthread_cancel(tid);//线程创建立马取消
    cout<<"pthread_cancel: "<<tid<<endl;
    //主线程做自己的事
    int count=0;
    while(true)
    {
        cout<<"main线程: "<<"running ..."<<endl;
        sleep(1);
        count++;
        if(count>=5) break;
    }
    int*ret = nullptr;
    pthread_join(tid,(void**)&ret);//线程id,等待tid线程
    cout<<"main thread wait done ... main quit ...:new thread qiut : "<<(int)ret<<"\n";
    return 0;
}

一上来就取消的这种行为是未定义的,异常,进程退出

细节三:
我们一般都是用主线程去取消其他线程,而不要用新线程来取消,因为主线程承担的一个核心工作就是调用pthread_join去等待新线程,如果让新线程去cancel,如果cancel的是主线程,那么谁来处理join

四、线程id的探索

格式化打印tid

printf("%lu,%p",tid,tid);
//%u表示打印unsigned int
//%lu表示打印unsigned long int

在这里插入图片描述

可以看出来,这个线程id怎么这么大
线程id是一个id,但本质上他是一个地址
线程id不仅仅是用地址来表示唯一id
而且因为我们目前用的不是Linux自带的创建线程的接口,而是pthread库中的接口
线程也要被管理起来,OS承担一部分,库承担一部分

操作系统承担的主要是对轻量级进程的调度以及对内核数据结构的管理,而库给用户提供线程相关的属性字段,包括线程id,线程私用栈大小等等

线程在执行的时候需要有自己的私有栈,因为多个线程是共享的,不进行私有化处理的话就太乱了,比如说你的数据压栈,我的又压栈,用的时候顺序太混乱了,因为OS的设计就只有轻量级进程,各奔感知不到线程的存在,所以每个线程的私有栈必须在用户层提供(内核只管共享)

在这里插入图片描述
既然在pthread共享库中要给每个线程都维护tid,线程局部存储,线程栈这样的结构,那么既然有线程栈,地址空间中的栈还用不用呢?
当然要用,主线程就是用的地址空间中的栈,所以这样就保证了每个线程都有自己的独立栈,还不与只有一个执行流的时候用的是地址空间栈冲突!!!

底层调用clone

底层通过调用clone创建轻量级进程
在这里插入图片描述
参数一:回调函数
参数二;这个轻量级进程在用户层手动设置的栈结构,来充当这个新线程的栈 结构,其他选项我们就不管了

pthread库,在创建这个轻量级进程的PCB的时候,调用clone,在库内部申请线程相关的属性字段(线程id,线程局部存储,线程栈),然后把栈的地址传入给新线程,这个新线程在调度时就会用共享区的栈区(因为pthread是动态库,加载到共享区)

但是呢,因为共享区又是在地址空间,被所有线程共享,所以,其实并没有很严格的把这个线程栈私有化,我们只要愿意,就可以拿到 地址去访问其他线程栈,但一般来说不会这样做

获取线程id

pthread库也提供了每一个线程获取自己线程id的接口
pthread_self
在这里插入图片描述

既然如此,那么我们很容易就想到可以下面这样做

pthread_cancel(pthread_self());

自己取消自己,建议不要搞这些花里胡哨的操作,很多这种操作都是未定义的,就用主线程去控制就可以了

全局数据 共享与私有

int g_val = 0;//全局变量被多线程共享访问
//新线程对g_val做修改,如果共享,那么主线程拿到的也会被修改
void*threadRoutine(void*args)
{
	cout<<(char*)args<<" : "<< g_val <<  " &: "<<&g_val<<endl;
	sleep(1);
	g_val++;	
}

int main()
{
	pthraed_t tid;
	pthread_create(&tid,nullptr,threadRountine,(void*)"thread 1");
	while(true)
	{
		cout << "main thread" << " : " << g_val << " &: " << &g_val << endl;
		sleep(1);
	}
	
	return 0;
}

在这里插入图片描述
这个全局变量是放在进程空间的,所以能被线程共享

但是如果我想独占一个g_val变量,那么前面需要加上__thread

__thread int g_val = 0;

在这里插入图片描述
__thread修饰全局变量,带来的结果就是每一个线程都拥有一个独自的全局变量,这就是线程的局部存储,维护在库当中

五、分离线程

创建线程我们可以去join,但是呢,join的时候线程是阻塞的,那主线程能不能不要关系了呢?也就是说新线程执行完了之后,告诉系统,自动释放线程资源,主线程不关心

线程分离pthread_detach
在这里插入图片描述
作用就是给线程设置成分离状态,主线程不需要等他,自动释放,不会造成僵尸问题

线程分离呢一般是让线程自己分离自己

pthread_detach(pthread_self());

如果说新线程已经被分离,主线程还在去join的话,那么join就会返回错误信息(以错误码的形式,可以通过strerror(errno)来显示)

新线程的僵尸问题我们是看不到的,因为他是库里面的代码,怎么和OS交互我们并不清楚,所以看不见现象

线程分离的问题

对于线程分离,主线程可以不用等这个新线程了,那是不是可以主线程直接走了呢?
①主线程退出就意味着进程退出,那么所有执行流都会终止,有可能这个分离的线程还有事要干
②无论在多进程还是多线程场景下,都应该主进程或者主线程最后退出,因为它不仅仅要担任创建,还有资源回收的职责
③对于服务器来说,主线程或者主进程都是不退出的,所以怎么用还得取决于场景
④不要乱用
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猪皮兄弟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值