【Linux】线程控制

文章详细介绍了Linux下线程的概念,包括Linux将线程视为轻量级进程以及如何通过POSIX线程库(pthread)进行线程的创建、终止、线程ID和地址空间的关系。示例代码展示了如何使用pthread_create创建线程,并讨论了pthread_exit和pthread_cancel函数在终止线程时的作用。此外,还提到了线程等待和线程分离的功能,以及线程库如何管理和描述每个线程的结构体。
摘要由CSDN通过智能技术生成

目录

1 理解Linux的进程相关的函数

2 POSIX线程库的函数介绍

2.1 创建线程

 2.2 pthread_ create函数:线程ID及进程地址空间布局

 2.3 线程终止

pthread_exit()函数

pthread_cancel函数--主线程终止其它线程

 2.4 线程等待

2.5 线程分离

3 如何理解线程库


1 理解Linux的进程相关的函数

  • 用户的角度:只认线程
  • Linux的角度:没有线程,只有用进程模拟的线程(轻量级进程LWP)--所以Linux没有线程的系统调用,它只会提供给我们创建轻量级进程的接口
  • 所以为了让用户看上去使用的是线程的接口,Linux原生库就带有了POSIX:pthread库,里面封装了Linux对进程控制的函数!
  • pthread是一个动态库,在g++编译的时候,要指明动态库的名称,-lpthread

2 POSIX线程库的函数介绍

2.1 创建线程

#include <iostream>

using namespace std;

#include <cstring>

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

void *threadRun(void *args)
{
    while (true)
    {
        cout << "new thread run ..." << endl;
        sleep(1);
    }
}

int main()
{
    pthread_t t;
    int ret = pthread_create(&t, nullptr, threadRun, nullptr);
    if (ret != 0)
    {
        cerr << "errno:" << ret << ": " << strerror(ret) << endl;
    }
    while (true)
    {
        cout << "main thread run ..." << endl;
        sleep(1);
    }
    return 0;
}

 2.2 pthread_ create函数:线程ID及进程地址空间布局

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

pthread_t pthread_self(void);

pthread_t 到底是什么类型呢?取决于实现。对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。

 2.3 线程终止

如果需要只终止某个线程而不终止整个进程,可以有三种方法:

  1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
  2. 线程可以调用pthread_ exit终止自己
  3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。

pthread_exit()函数

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

pthread_cancel函数--主线程终止其它线程

 2.4 线程等待

  • 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
  • 创建新的线程不会复用刚才退出线程的地址空间。

调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下: 

  1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
  2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_ CANCELED//#define PTHREAD_CANCELED ((void *) -1)。
  3.  如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
  4. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。

 【多线程并行计算多个累加值】

#include <iostream>
#include<string>
using namespace std;

#include <cstring>

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

#define NUM 5

struct ThreadData
{
    ThreadData(int top,int result,const string& name)
        :_top(top)
        ,_result(result)
        ,_name(name)
    {}

    int _top;
    int _result;
    string _name;
    int status=0;
};


void *threadRun(void *args)
{
    ThreadData*td=(ThreadData*)args;
    for(int i=1;i<=td->_top;++i)
    {
        td->_result+=i;
    }
    return td;
}

int main()
{
    pthread_t t[NUM];

    for(int i=0;i<NUM;++i)
    {
        ThreadData* args=new ThreadData(100+i*5,0,string("thread")+to_string(i));
        int ret = pthread_create(t+i,nullptr,threadRun,args);
        if(0!=ret)
        {
            cout<<"errno:"<<ret<<": "<<strerror(ret)<<endl;
        }
    }
    for(int i=0;i<NUM;++i)
    {
        void*ret=nullptr;
        pthread_join(t[i], &ret);
        ThreadData* args=static_cast<ThreadData*>(ret);
        if (args->status == 0)
        {
            cout << args->_name << " count [1," << args->_top << "] = " << args->_result << endl;
        }
        delete args;
    }
    cout<<"all thread quit!"<<endl;
    return 0;
}

2.5 线程分离

  • 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
  • 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。

int pthread_detach(pthread_t thread);

可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离:

pthread_detach(pthread_self());

joinable和分离是冲突的,一个线程不能既是joinable又是分离的。


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *thread_run(void *arg)
{
    pthread_detach(pthread_self());
    printf("%s\n", (char *)arg);
    return NULL;
}
int main(void)
{
    pthread_t tid;
    if (pthread_create(&tid, NULL, thread_run, "thread1 run...") != 0)
    {
        printf("create thread error\n");
        return 1;
    }
    int ret = 0;
    sleep(1); // 很重要,要让线程先分离,再等待
    if (pthread_join(tid, NULL) == 0)
    {
        printf("pthread wait success\n");
        ret = 0;
    }
    else
    {
        printf("pthread wait failed\n");
        ret = 1;
    }
    return ret;
}

3 如何理解线程库

问题:如何理解每一个线程都有自己的线程ID、一组寄存器、栈

答:一个进程中有着多个线程,而线程的创建是由pthread动态库来创建的,这么多的线程,也是需要管理的,所以pthread库来对它们进行管理!要管理,就要先描述,在组织!--》pthread库会在进程地址空间的共享区中对每一个进程创建一个类似的TCB的结构体!每一个线程的结构体,像“数组”似得聚合到共享区中,此时每一块的结构体的首地址就是每一个线程所对应的线程ID;在结构体内部,会有着自己线程栈的结构--因为栈的数据是由一组寄存器esp,ebp所维护的,在CPU调度不同的线程的时候,每一个线程所对应的esp,ebp里的数值不同,这样就可以准确的切换线程了。

所有线程都需要有自己独立的栈结构,主线程用的是进程系统栈,新线程用的是库中提供的栈结构! 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

杰深入学习计算机

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

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

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

打赏作者

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

抵扣说明:

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

余额充值