线程与多线程(一)

一、线程

1、概念

  • 线程也被称为轻量级进程(Lightweight Process,LWP),它是程序执行流的最小单元、操作系统调度的基本单位。用户级执行流与内核LWP间是一一对应的。
  • 在整个生命周期中,相比于进程,线程的创建和释放更加轻量化;进程内线程的切换不需要重新cache数据,进程内线程的切换(运行)更加轻量化。
  • 在Linux中没有真正义上的线程,所谓的线程是用进程(内核数据结构)模拟的线程。
  • 一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。但由于线程之间的相互制约,线程在运行中呈现出间断性。
  • 在进程的虚拟地址空间中,进程拥有大部分资源。将进程的这些资源合理分配给每个执行流就形成了线程执行流。即进程是承担分配系统资源(本质为地址空间范围)的基本实体,线程是进程内部的执行流资源。所以,线程是进程内的一个执行分支,它执行一部分进程的代码,执行粒度要比进程细。
  • 每一个程序都至少拥有一个线程,即程序本身。而线程是程序中一个单一的顺序控制流程。所以,多线程是在单个程序(进程)中同时运行多个线程完成不同的工作。

2、示意图

在这里插入图片描述

3、虚拟地址转换到物理地址

在这里插入图片描述

4、与进程相比的优点

  • 创建一个新线程的代价较小。
  • 线程之间的切换需要操作系统做的工作较少。
  • 占用的资源较少,能充分利用多处理器的可并行数量。
  • 在等待慢速I/O操作结束的同时,程序可执行其他计算任务。
  • 在I/O密集型应用中,可将I/O操作重叠来提高性能。这样线程就可以同时等待不同的I/O操作。
  • 计算密集型应用时,可将计算分解到多个线程中以实现在多处理器系统上运行的目的。
  • 合理的使用多线程,能提高CPU密集型程序的执行效率、IO密集型程序的用户体验。

5、与进程相比的缺点

  • 性能损失:一个很少被外部事件阻塞的计算密集型线程往往无法与其它线程共享同一个处理器。
  • 健壮性降低:在一个多线程程序里,因为时间分配上的细微偏差或者因为共享了不该共享的变量而造成不良影响的可能性较大。
  • 缺乏访问控制:因为进程是访问控制的基本粒度。所以,在一个线程中调用某些系统函数会对整个进程造成影响。
  • 编程难度高:编写与调试一个多线程程序比单线程程序困难。
  • 影响较大:因为线程是进程的执行分支。所以,线程出现异常就类似于进程出现异常。当触发信号机制终止时,整个进程都将终止,该进程内的所有线程也将退出。即如果一个线程崩溃了,这个线程所在的进程内的所有线程都将出错崩溃。

6、与进程的关系

(1)线程独有

  • 线程ID、一组寄存器、栈空间、errno、信号屏蔽字、调度优先级。

(2)共享

  • 文件描述符表、每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)、当前工作目录、用户id和组id。

(3)示意图

在这里插入图片描述

二、POSIX线程库

  • 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以 pthread_ 开头。
  • 当使用POSIX线程库的函数时,需要引用头文件pthread.h
  • 当链接POSIX线程库时,需要使用编译器命令的 -lpthread 选项。

三、创建线程

1、函数

在这里插入图片描述

2、概念

  • pthread_create函数在调用进程中启动一个新线程,该新线程通过调用参数start_routine所指向的函数开始执行,参数arg作为start_routine指向的函数的唯一传递的参数。
  • 参数attr指向一个pthread_attr_t结构体,其内容在线程创建时用于确定新线程的属性。该结构使用pthreadattr_init和相关函数进行初始化。如果attr为NULL,则使用默认属性创建线程。
  • 在返回之前,如果成功调用了pthread_create函数,该函数会将新线程的ID存储在线程指向的缓冲区中。此标识符(参数thread,即线程ID)用于在后续调用其他pthread函数时引用对应的线程。
  • 新线程继承了调用创建线程函数的线程的信号掩码(pthread_sigmask)的副本,浮点环境(fenv)。但不继承其备用信号堆栈(sigaltstack)。
  • 新线程挂起信号集(sigpending)为空,CPU时间时钟的初始值为0。

四、pthread_self

1、函数

在这里插入图片描述

2、概念

  • pthread_self()函数返回调用此函数的线程的线程ID。这与创建此线程的线程在调用pthread_create函数成功后,参数thread返回的值相同。

3、pthread_t

  • 在Linux目前实现的NPTL实现来说,pthread_t类型的线程ID本质是一个进程地址空间上的一个地址。

4、示意图

在这里插入图片描述

5、说明

  • 在上方的示意图中,struct pthread、线程局部存储和线程栈的一整块是线程的tcb,即线程控制块。而每一个线程的库级别的tcb的起始地址是线程的tid,类型为pthread_t。
  • 在进程中,除了主线程以外的其他所有线程的独立栈都在共享区中。具体是在pthread库中,tid指向的用户tcb中。
  • 每一个线程都有自己独立的栈结构,线程栈上的数据可以被同一进程内的其他线程看到并访问。
  • 如果线程想要一个私有的全局变量,可以在全局变量的定义前面加__thread编译选项。这是线程的局部存储,只能定义内置类型而不能用来修饰自定义类型。

五、线程终止

1、方式

  • 调用pthread_exit函数,指定一个退出状态值,该值可供调用pthread_join函数的同一进程中的另一个线程使用。
  • 从start_routine指向的函数返回。这相当于使用return语句中提供的值调用pthread_exit函数。
  • 被pthread_cancel函数取消。
  • 进程中的任何线程调用exit函数,或者主线程执行main函数的返回。都会导致进程中所有线程的终止。

2、pthread_exit

(1)函数

在这里插入图片描述

(2)概念

  • pthread_exit函数终止调用该函数的线程,并通过retval返回一个值,该值(如果线程是joinable的)可供调用pthread_join的同一进程中的另一个线程使用。
  • 当线程终止时,进程共享资源(例如互斥锁、条件变量、信号量和文件描述符)不会被释放。
  • 在进程中的最后一个线程终止后,该进程通过调用退出状态为零的exit来终止,进程共享资源将被释放。
  • pthread_exit函数或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在将退出的线程所执行的函数的栈上分配。因为当其它线程得到这个返回指针时,该线程已经退出了。此时访问该内存单元的结果是未知的。

3、pthread_cancel

(1)函数

在这里插入图片描述

(2)概念

  • pthread_cancel函数向thread线程发送取消请求。目标线程是否以及何时对取消请求做出反应取决于该线程控制的两个属性,即其可取消性状态和类型。
  • 被取消的线程终止后,调用pthread_join等待该线程的线程将获得 PTHREAD_CANCELED 作为该线程的退出状态。Joining(等待)一个线程是知道取消操作是否已完成的唯一方法。
  • 一个线程可以调用pthread_ cancel函数终止同一进程中的另一个线程。

六、线程等待

1、函数

在这里插入图片描述

2、概念

  • pthread_join函数等待thread指定的线程终止。如果该线程已经终止,则pthread_join函数立即返回,否则阻塞等待。thread指定的线程必须是joinable(可等待)的。
  • 如果retval不为NULL,则pthread_join函数将目标线程的退出状态,即目标线程提供给pthread_exit的值或者执行函数结束时返回的值,复制到retval指向的位置。如果目标线程被取消,则 PTHREAD_CANCELED 将被放置在retval中。
  • 如果多个线程同时尝试等待同一个线程,则结果是未定义的。如果调用pthread_join函数的线程被取消,那么目标线程将保持joinable状态。即它不会被detached(分离)。

3、意义

  • 如果不进行等待,则已经退出的线程的空间不会被释放,仍然在进程的地址空间内,有可能造成内存泄漏。
  • 如果不进行等待,创建新的线程不会复用退出线程的地址空间,而当申请线程的数量达到一定值时,有可能会内存不足。

七、线程库

  • 内核中没有很明确得线程概念,只有轻量级进程的概念。所以,Linux操作系统不会直接提供线程相关的系统调用,只会提供轻量级进程的系统调用。
  • pthread线程库是在应用层对轻量级进程接口进行封装,为用户提供直接线程的接口。
  • 几乎所有的Linux平台都默认自带pthread线程库。当在Linux中编写多线程代码时,需要使用第三方的pthread库。
  • 因为线程库需要维护线程的概念而不用维护线程的执行流。所以,库需要对线程进行管理,使用时需要加载到内存中,即原生线程库是基于内存的。

八、示例代码

1、代码

string ToHex(pthread_t tid)
{
    char buff[64];
    snprintf(buff, sizeof(buff), "%p", tid);
    return buff;
}

void *RunRoutine(void *arg)
{
    char *threadName = (char*)arg;
    for(int i = 0; i < 5; ++i)
    {
        cout << threadName << ", tid: " << ToHex(pthread_self()) <<  ", is running " << i << endl;
        sleep(1);
        //pthread_exit((void*)1);
        if(i == 2)
        {
            //exit(1);
            //return (void*)1;
        }
    }

}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, RunRoutine, (void*)"thread 1");
    cout << "main thread create work done, new thread id: " << ToHex(tid) << endl;
    // sleep(1);
    // pthread_cancel(tid);
    // cout << "main thread: " << getpid() << " , thread tid " << ToHex(tid) << " canceled" << endl;
    // sleep(1);
    int ret = pthread_join(tid, nullptr);
    cout << "pthread_join wait over, ret = " << ret << endl;

    return 0;
}

2、运行结果

在这里插入图片描述

  • 放开pthread_exit行注释。

在这里插入图片描述

  • 放开exit行注释。

在这里插入图片描述

  • 放开return行注释。

在这里插入图片描述

  • 放开main内注释。

在这里插入图片描述

九、分离线程

1、函数

在这里插入图片描述

2、概念

  • pthread_detach函数可将thread标识的线程标记为已分离,但尝试分离已分离的线程的行为是未定义的。
  • 当一个分离的线程终止时,它的资源会自动释放回系统,而不需要另一个线程去等待它。
  • 可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离。
  • joinable和分离是冲突的,即一个线程不能既是joinable又是分离的。

3、代码

void *RunRoutine(void *arg)
{
    pthread_detach(pthread_self());
    char *threadName = (char*)arg;
    for(int i = 0; i < 5; ++i)
    {
        cout << threadName << ", tid: " << pthread_self() <<  ", is running " << i << endl;
        sleep(1);
    }

}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, RunRoutine, (void*)"thread 1");
    cout << "main thread create work done, new thread id: " << tid << endl;
    sleep(6);
    cout << "sleep time over" << endl;
    return 0;
}

4、运行结果

在这里插入图片描述

本文为线程相关的内容,多线程相关的内容参见线程与多线程(二)

本文到这里就结束了,如有错误或者不清楚的地方欢迎评论或者私信
创作不易,如果觉得博主写得不错,请点赞、收藏加关注支持一下💕💕💕

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值