Linux多线程

1. Linux多线程的概念

1.1 线程引入

在这里插入图片描述
在Linux系统中,每个进程都有自己的地址空间,它由多个段(区)组成,包括代码区、字符常量区、已初始化数据区、未初始化数据区、堆、栈以及内核区等,所谓地址空间是指进程可用于寻址内存的一系列地址。 CUP内的寄存器指向进程的PCB(PCB是指操作系统内核中用于管理进程的数据结构),当然还有寄存器指向栈顶,页表等;进程的PCB指向它自己的地址空间,这样一个进程间就被建立起来。

如果再创建一个子进程,那么需要创建自己的地址空间、页表等,有一种方法只创建PCB,不再创建地址空间等,而和父进程共用一套地址空间、页表。在代码区可以设置不同条件,相当于不同的条件下调用不同的函数;同一个进程的每一个不同的PCB相当于一个执行流,他们执行相同代码区里的不同代码,像这样进程里创建多个PCB,将这种PCB称为线程。
如果说操作系统中引入进程的目的是使多个程序能并发执行,以提高资源利用率和系统吞吐量,那么再引入线程则是为了减少程旭在并发执行时所付出的时间和空间开销,以使操作系统具有更好的并发性。

1.2 线程的概念

线程是计算机科学中的一个概念,它是进程中的一个实体,是CPU调度和分派的基本单位。 线程与进程的区别在于,线程是进程中的一个实体,而进程则是操作系统资源分配的基本单位。 在Linux系统中,线程被称为轻量级进程(LWP),因为它们与其父进程共享许多资源,如地址空间、打开文件等。

CPU与操作系统的概念:CPU(中央处理器)是计算机的核心部件,负责执行指令,控制计算机的运行。操作系统则是计算机的软件部分,它管理计算机的硬件资源,为应用程序提供服务。CPU和操作系统之间的区别在于,CPU是计算机硬件的一部分,而操作系统是计算机软件的一部分。 CPU负责执行指令,而操作系统负责管理和控制计算机的各种资源,如内存、硬盘、输入输出设备等。此外,操作系统还提供了一些基本服务,如文件管理、进程管理、内存管理等。所以线程的粒度比进程的粒度更细。

Linux下的多线程编程可以使用pthread库实现。Linux系统中并没有真正意义上的多线程,因为linux内核中并没有为线程构建数据结构,而是用进程方案模拟线程。(windows是有真线程的) 使用pthread库,可以使用以下函数实现多线程:

  1. pthread_create:创建线程
  2. pthread_join:等待线程结束
  3. pthread_exit:退出线程
  4. pthread_self:获取线程ID
  5. pthread_detach:分离线程。

以上函数都是在头文件pthread.h中定义的。其中,pthread_create()函数用于创建一个新的线程,它的原型如下:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
  • thread参数是一个指向线程标识符的指针,当线程创建成功后,用来返回创建的线程ID;
  • attr参数用于指定线程的属性,NULL表示使用默认属性;
  • start_routine参数为一个函数指针,指向线程创建后要调用的函数,这个函数也称为线程函数;
  • arg参数指向传递给线程函数的参数。

返回值:线程创建成功则返回0,发生错误时返回错误码。

在Linux系统下,多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。Linux下pthread是通过系统调用clone()来实现的。clone()是Linux所特有的系统调用,它的使用方式类似于fork()。

1.3 线程的优缺点

线程的优点:

  1. 创建一个新线程的代价要比创建一个新进程小得多
  2. 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
  3. 线程占用的资源要比进程少很多
  4. 能充分利用多处理器的可并行数量
  5. 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
  6. 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
  7. I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

线程的缺点:

  1. 性能损失:一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
  2. 健壮性降低:编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
  3. 缺乏访问控制:进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
  4. 编程难度提高:编写与调试一个多线程程序比单线程程序困难得多。

总的来说,多线程的优点是可以提高程序的并发性和效率,缺点是增加了系统调用的代价和内核资源的消耗。

1.4 线程执行

在Linux中,线程是复用进程数据结构,那么Linux怎么识别一个执行流是线程还是进程?怎么确定线程是并发执行的?

#include <iostream>
#include <pthread.h> //包含线程的头文件
#include <unistd.h>
using namespace std;

void *thread1_run(void* args) 
{
    while (1)
    {
        cout << "我是线程1,我正在运行" << endl;
        sleep(1);
    }
}
void *thread2_run(void* args)
{
    while (1)
    {
        cout << "我是线程2,我正在运行" << endl;
        sleep(1);
    }
}
void *thread3_run(void* args)
{
    while (1)
    {
        cout << "我是线程3,我正在运行" << endl;
        sleep(1);
    }
}
int main()
{
    pthread_t t1, t2, t3;
    pthread_create(&t1, nullptr, thread1_run, nullptr);//线程属性还有传递线程函数的参数设为空
    pthread_create(&t2, nullptr, thread2_run, nullptr);
    pthread_create(&t3, nullptr, thread3_run, nullptr);
    
    while (1)
    {
        cout << "我是主线程,我正在运行" << endl;
        sleep(1);
    }
    return 0;
}

由下图可以看出,有四个死循环,四个循环同时打印,说明线程是并发式执行的;如果不是并发执行,那么会卡到某一个循环中。(用g++编译时,如果头文件包含pthread.h,则编译时需要链接pthread库,如:g++ -o mythread mythread.cc -lpthread
在这里插入图片描述
使用指令ps -aL | head -1 && ps -aL | grep mythread(文件名) ,查看线程信息,可以看到他们的PID是相同的,但是LWP不同,所以线程是用LWP来区分的。LWP是轻量级进程,在Linux下进程是资源分配的基本单位,线程是cpu调度的基本单位,而线程使用进程pcb描述实现,并且同一个进程中的所有pcb共用同一个虚拟地址空间,因此相较于传统进程更加的轻量化。
在这里插入图片描述
多线程程序中,任何一个线程崩溃了,最后都会导致进程崩溃;系统角度:线程是进程的执行分支,线程崩溃,进程就崩溃了。

2. 有关线程的函数

1.pthread_join()函数是用来等待一个线程结束,并获取它的返回值的。它的参数有两个:

pthread_t thread:要等待的线程的标识符,也就是线程ID。
void **retval:一个指向指针的指针,用来存储线程的返回值。如果不需要获取返回值,可以传入NULL。
pthread_join()函数会阻塞调用它的线程,直到目标线程结束为止。如果目标线程已经结束,或者是分离状态的,那么pthread_join()函数会失败,并返回相应的错误码。

2.pthread_exit()函数是用来终止调用它的线程的,它可以通过一个指针参数返回线程的退出状态,供其他线程获取。它还会执行线程清理函数和线程特定数据的析构函数。pthread_exit()函数不会释放进程共享的资源,也不会调用atexit()注册的函数。如果主线程调用了pthread_exit()函数,那么其他线程仍然可以继续执行,直到进程结束。pthread_exit()函数和return语句的区别是,return语句会导致整个进程终止,而pthread_exit()函数只会终止当前线程。

3.pthread_cancel()函数是用来向一个线程发送终止请求的,它的语法格式是:

int pthread_cancel(pthread_t thread);
其中,thread参数是要终止的线程的标识符。如果函数成功地发送了终止请求,返回0;否则返回错误码。注意,发送成功并不意味着线程会立即终止,而是取决于线程的取消状态和取消类型。
线程的取消状态有两种:启用(PTHREAD_CANCEL_ENABLE)和禁用(PTHREAD_CANCEL_DISABLE)。启用状态下,线程会响应终止请求;禁用状态下,线程会忽略终止请求,直到重新启用为止。

4.pthread_create函数

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
  • thread参数是一个指向线程标识符的指针,当线程创建成功后,用来返回创建的线程ID;
  • attr参数用于指定线程的属性,NULL表示使用默认属性;
  • start_routine参数为一个函数指针,指向线程创建后要调用的函数,这个函数也称为线程函数;
  • arg参数指向传递给线程函数的参数。
    对于pthread_create函数中的参数arg不但能传int,char,double等内置类型的数据,还能传自定义类型的数据,如结构体等。如下为参数arg为结构体的例子,并且每个线程做不同的事情。
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

#define NUM 10
enum{ OK=0, ERROR };

class ThreadData
{
public:
    ThreadData(const string &name, int id, time_t createTime, int top)
    :_name(name), _id(id), _createTime((uint64_t)createTime),_status(OK), _top(top), _result(0)
    {}
    ~ThreadData()
    {}
public:
    string _name;
    int _id;
    uint64_t _createTime;

    // 返回的
    int _status;
    int _top;
    int _result;
};

void *thread_run(void *args)
{
    ThreadData *td = static_cast<ThreadData*>(args);

    for(int i = 0; i <= td->_top; i++)
    {
        td->_result += i;
    }
    cout << td->_name << " cal done!" << endl;
    
    return td;

}

int main()
{
    pthread_t tids[NUM];
    for(int i = 0; i < NUM ;i++)
    {
        char tname[64];
        snprintf(tname, 64, "thread-%d", i+1);
        ThreadData *td = new ThreadData(tname, i+1, time(nullptr), i + 1);
        pthread_create(tids+i, nullptr, thread_run, td);
        sleep(1);
    }

    void *ret = nullptr; 

    for(int i = 0 ; i< NUM; i++)
    {
        int n = pthread_join(tids[i], &ret);
        if(n != 0) cerr << "pthread_join error" << endl;
        ThreadData *td = static_cast<ThreadData *>(ret);
        if(td->_status == OK)
        {
            cout << td->_name << " 计算的结果是: " << td->_result << " (它要计算的是[0, " << td->_top << "])" <<endl;
        }

        delete td;
    }
}

运行结果如下:
在这里插入图片描述

评论 24
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ly@눈_눈

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

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

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

打赏作者

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

抵扣说明:

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

余额充值