Linux:多线程(一.Linux线程概念、线程控制——创建、等待、退出、分离,封装一下线程)

2024-08-02 08:17:24 浏览数 (2)

1. Linux线程概念

1.1概念

在Linux系统中,线程是指在同一个进程中并发执行的多个执行序列。每个线程都有自己的程序计数器、寄存器集合、栈空间、线程特有数据等,但它们共享同一个进程的地址空间和其他资源。Linux系统中的线程是由内核进行调度和管理的,因此线程之间的切换是由内核来控制的。 以下是一些关于Linux线程的重要概念:

  1. 轻量级:Linux线程是轻量级的执行单元,它们共享进程的资源,如内存空间、文件描述符等。相比于进程,线程的创建、销毁和切换开销更小。
  2. 并发执行:在一个进程中可以创建多个线程,这些线程可以并发执行,从而提高程序的性能和响应速度。每个线程可以执行不同的任务,或者并发执行同一任务的不同部分。
  3. 共享资源:线程之间**共享同一进程的资源**,包括全局变量、静态变量、堆内存等。这种共享使得线程之间可以方便地进行通信和共享数据。
  4. 独立栈空间:每个线程有自己的栈空间,用于存储函数调用、局部变量等信息。线程的栈空间是独立的,但位于同一进程的地址空间中。
  5. 线程同步:由于线程之间共享资源,因此需要进行线程同步来避免竞争条件和数据访问冲突。Linux提供了多种线程同步机制,如互斥锁、条件变量、信号量等。
  6. 线程调度:Linux内核负责对线程进行调度,根据优先级、调度策略等进行线程切换。Linux支持多种调度策略,如先进先出(FIFO)、循环调度(Round-Robin)等。
  7. 线程状态:Linux线程可以处于运行、就绪、阻塞等不同状态,内核根据线程的状态来进行调度和管理。
  • 线程是进程内部的一个执行分支,线程是CPU调度的基本单位,能够实现高效的并发执行
  • 通过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流
  • 执行流是指程序在运行过程中的控制流程,即程序的执行路径或者执行顺序。线程是进程内部的执行单元,每个线程都有自己的执行流
  • 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流

1.2线程的理解

fork()创建的子进程中,子进程会从fork()调用的位置开始执行,继续执行父进程的代码。父进程和子进程在fork()调用之后会并发执行,每个进程有自己独立的执行流,它们可以独立地执行不同的任务或者并发执行相同的任务。 因此,子进程可以被看作是父进程的一个执行流,它继承了父进程的一部分代码和状态,但是拥有自己独立的执行环境

但是,每次创建子进程都要复制父进程的地址空间、代码段、数据段等资源,然后将子进程的执行流独立地运行起来。这代价也不小。所以线程的优势便体现出来:创建一个新线程的代价要比创建一个新进程小得多

task_struct现在是一个个的线程了

正文:代码段(区),我们的代码在进程中,全部都是串行调用的 地址空间和地址空间上的虚拟地址,本质是一种"资源 将不同的代码段分配给不同的线程,使得线程能够并发执行,从而将串行执行转变为并发执行。通过将不同的代码段分配给不同的线程,并发执行可以充分利用多核处理器的性能,提高系统的并发能力

线程在进程的地址空间内运行

Linux中线程设计的巧思

首先我们来设想一下:让我们自己来设计线程怎么设计

但是我们也想到这些逻辑,我们在设计进程时已经设计过了

Linux的设计者认为,进程和线程都是执行流,具有极度的相似性,没必要单独设计数据结构和算法直接复用代码 使用进程来模拟线程,所以Linux中没有真正意义上的线程,采用了巧妙的复用

1.3Linux线程VS进程

  • 我们之前学习的进程:内部只有一个线程的进程
  • 现在的进程:一个内部至少有一个线程的进程
  • 而我们一直说进程=内核数据结构(PCB、地址空间、页表等总和) 代码数据。也是一直成立了,不过可以有多个PCB

进程的内核角度:承担分配系统资源的基本实体

线程:就是一个一个执行流,是进程内的执行分支

从内核角度来看,进程是操作系统分配系统资源的基本实体,包括内存空间、文件描述符、CPU时间等。每个进程都有自己的地址空间和资源,是操作系统中最基本的执行单位。

而线程则是进程内的执行流,是进程的一个执行分支。线程共享进程的地址空间和资源,但拥有独立的执行流和栈空间。线程之间可以更快地进行通信和数据共享,同时也能更高效地实现并发执行。

在操作系统中,进程和线程的关系可以理解为:一个进程可以包含一个或多个线程,每个线程都是进程的一部分,共享进程的资源。进程和线程之间的关系是一种包含和被包含的关系,进程是线程的容器,线程是进程的执行单元。

1.4调度与轻量级进程

在Linux系统中,所有的执行流都被称为轻量级进程(Lightweight Process,LWP),实际上就是操作系统概念中的线程。在Linux中,线程和进程的区别并不是很明显,因为Linux将线程实现为与进程相似的实体,即轻量级进程。

在Linux中,每个轻量级进程(线程)都对应一个task_struct结构体,操作系统通过调度算法选择下一个要执行的轻量级进程,而不关心这个task_struct属于哪个进程,或者是属于一个进程的其中一个线程。

因此,在Linux中,CPU调度的实际执行单元是轻量级进程(线程),而不是进程。每个轻量级进程都有自己的执行流,可以独立执行代码,拥有独立的栈空间和寄存器状态。多个轻量级进程可以共享一个进程的资源,实现并发执行。

在Linux中,所有的执行流都被称为轻量级进程(线程),操作系统通过调度算法选择下一个要执行的轻量级进程,而不关心它们属于哪个进程。这种设计使得线程和进程之间的切换更加高效,同时也方便了并发编程和多任务处理。

1.5程序如何划分(重拾页表、见一下LWP)

操作系统进行内存管理的基本单位是4KB 内存里都是以4kb大小分的一个一个内存块——空间 可执行程序也是以4kb进行分——内容

现在我们再来重新看待页表:

后12位叫做叶内偏移 同时读取时还要结合数据类型的大小

给不同的线程分配不同的区域,本质就是给让不同的线程,各自看到全部页表的子集

代码语言:javascript复制
#include <iostream>
#include <thread>
#include <unistd.h>
#include <sys/types.h>

using namespace std;

void *test(void *arg)
{
    while (true)
    {
        std::cout << "I am new thread, pid: " << getpid() << std::endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, test, nullptr);
    while (true)
    {
        std::cout << "I am main thread, pid: " << getpid() << std::endl;
        sleep(1);
    }
    return 0;
}

函数编译完成,是若干行代码块,函数名是该代码块的入口地址。每一行代码都有地址 -----虚拟地址(逻辑地址) 最终形成的可执行程序中的所有函数,都会按照地址空间统一编址。这意味着每个函数在虚拟地址空间中都有自己的地址范围

  • 一个主线程执行main()函数
  • 另一个线程执行其他函数

本质就是:代码按照不同的函数,把程序分为不同的区域。每一个执行流执行的是该函数对应的代码区域

  • LWP(lighting weight process)操作系统在进行调度的时候,用哪一个它来进行调度
  • 之前我们都是单线程,也能看到单线程时pid=LWP

ps -aLps 命令的一种用法,用于显示系统中的所有进程的线程信息。在这个命令中,选项 -a 表示显示所有进程(包括其他用户的进程),而选项 -L 表示显示进程的线程信息。


2.线程控制

2.1引言与原生库

首先我们知道Linux系统下是没有真正的线程的,只有轻量级进程。那么Linux系统,不会有线程相关的系统调用,只有轻量级进程的系统调用 但是普通用户不知道这件事,当其他系统的用户来使用Linux时就会默认为Linux没有线程(轻量级进程:明明我就是) 所以Linux就提供了一个库:pthread库(原生线程库,Linux系统提供)——将轻量级进程的系统调用进行封装,转成线程相关的接口语义提供给用户

我们g 编译器是会默认链接 C 标准库。因此,当您使用 std::vector 等,编译器会自动链接 C 标准库,无需显式指定 -lc 而在使用 pthread 时需要指定 -lpthread,是因为 pthread 是 POSIX 线程库,不是 C 标准库的一部分,需要显式链接 除了 pthread 库之外,Linux 系统还提供了许多其他原生库,用于实现各种功能和操作。一些常见的 Linux 原生库包括:

  1. libc:C 标准库,提供了基本的 C 语言函数和数据结构。
  2. libm:数学库,提供了数学函数和常量。
  3. libpthread:POSIX 线程库,用于多线程编程。
  4. librt:实时库,提供了一些实时特性的函数。
  5. libdl:动态链接库,用于动态加载和链接共享库。
  6. libcryptolibssl:加密库,提供了加密算法和 SSL/TLS 支持。

这些原生库都是 Linux 系统提供的标准库,可以在开发 Linux 应用程序时直接使用

2.2线程创建

pthread_create()与pthread_self()

pthread_create 函数用于在 POSIX 线程(pthread)中创建一个新的线程。以下是 pthread_create 函数的详细讲解:

函数原型

代码语言:javascript复制
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

参数说明

  • thread:输出型参数,指向新创建线程的标识符的指针(返回新线程id,方便我们管理)。在成功创建线程后,该指针将包含新线程的标识符。 pthread_t其实就是一个无符号长整型
在这里插入图片描述在这里插入图片描述
  • attr:用于指定线程的属性。如果为 nullptr,则使用默认属性(一般都是用默认)。
  • start_routine:指向线程函数的指针,即新线程将执行的函数。
  • arg:传递给线程函数的参数。其实就是调用start_ routine(arg);调用这个函数

返回值

  • 如果成功创建线程,返回值为 0。
  • 如果出现错误,返回值为错误码。

注意事项

  • 线程函数必须有 void* 类型的参数,并返回 void* 类型的结果。
  • 需要包含头文件 #include <pthread.h>
  • 在编译时需要链接 pthread 库,例如使用 -lpthread 选项。

pthread_self() 函数是 POSIX 线程库中的一个函数,用于获取当前线程的线程 ID(Thread ID)。它返回一个 pthread_t 类型的值

函数原型

代码语言:javascript复制
pthread_t pthread_self(void);

pthread_self() 函数没有参数,直接返回当前线程的线程 ID。

返回值pthread_self() 函数返回一个 pthread_t 类型的值,该值是当前线程的线程 ID

线程 ID 的用途: 线程 ID 是用来唯一标识一个线程的值。在多线程编程中,每个线程都有自己的线程 ID,可以用来区分不同的线程。线程 ID 在创建线程、管理线程、线程同步等操作中都有重要的作用。

代码语言:javascript复制
#include <iostream>
#include <thread>
#include <unistd.h>
#include <sys/types.h>

using namespace std;

void *test(void *arg)
{
    while (true)
    {
        std::cout << "I'm new thread, pid: " << " new thread id pthread_self:" << pthread_self() << endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, test, nullptr);
    while (true)
    {
        std::cout << "I'm main thread, pid: " << getpid() << " new thread id(tid): " << tid << " "
                  << " main thread id: " << pthread_self();
        sleep(1);
    }
    return 0;
}

在多线程编程中,无法确定哪个线程会先运行,因为线程的执行顺序由操作系统的调度器(scheduler)决定

线程的特殊情况
  • 新线程还在执行中,主线程如果退出了,那么新线程也会退出。 主线程结束了(操作系统会将整个进程标记为终止状态) == 进程退出 == 进程的所有资源都被释放 == 所有线程都退出 ==>> 我们一般都需要主线程最后结束
  • 在多线程编程中,无法确定哪个线程会先运行,因为线程的执行顺序由操作系统的调度器(scheduler)决定
  • 线程也是需要被等待的,不然也会发生内存泄露等问题

2.3线程等待

pthread_join()函数

pthread_join() 函数是 POSIX 线程库中的一个函数,用于等待指定的线程结束执行。具体来说,pthread_join() 函数会阻塞当前线程,直到指定的线程结束执行。一般情况下,主线程可以使用 pthread_join() 函数来等待其他线程的结束,以确保在主线程继续执行之前,其他线程已经完成了它们的任务。

代码语言:javascript复制
int pthread_join(pthread_t thread, void **retval);

参数:

  • thread:要等待的线程的标识符(通常是通过 pthread_create() 创建的线程返回的 pthread_t 结构体)。
  • retval:用于获取被等待线程的返回值(我们用来获取一些信息)。

返回值:

  • 返回值为0:表示函数调用成功,成功等待线程结束。
  • 返回值为错误码:表示函数调用出现错误,可以通过查阅相关文档来确定具体的错误原因。

2.4线程退出

线程退出只有三种情况:

  • 代码跑完了,结果是对的
  • 代码跑完了,结果是错的
  • 出现异常,代码没跑完

现在,我们已经能通过进程等待来获取代码执行结果,来确认是否是前两种情况

我们在一开始便点出一个结论:同一个进程内的线程,大部分资源都是共享的. 地址空间是共享的

所以:多线程中,任何一个线程出现异常(div 0, 野指针), 都会导致整个进程退出,这也是为什么pthread_join()函数不考虑异常的原因,由其父进程来考虑

那么线程该怎么退出呢?——使用exit()函数行吗?不行,使用exit也时进程直接结束了。

线程终止的方式:

  1. 线程函数结束
  2. 我们要使用pthread_exit()函数
pthread_exit()函数

pthread_exit() 函数是 POSIX 线程库中的一个函数,用于终止当前线程的执行并返回一个指定的值。当调用 pthread_exit() 函数时,当前线程会立即终止,不会影响其他线程的执行。

函数原型

代码语言:javascript复制
void pthread_exit(void *value_ptr);
  • value_ptr:指向当前线程的返回值的指针。线程的返回值可以是任意类型的数据,因为 value_ptr 是一个 void 指针。
  • 通过 value_ptr 参数可以传递线程的返回值,其他线程可以通过 pthread_join() 函数获取该返回值。

注意事项

  • 在线程执行过程中,可以随时调用 pthread_exit() 函数来终止线程。
  • 在主线程中调用 pthread_exit() 函数会终止整个进程,因为主线程的退出会导致整个进程的退出。
  • 如果线程没有显式调用 pthread_exit() 函数而是自然结束(执行完线程函数),线程的返回值默认为 NULL
代码语言:javascript复制
#include <iostream>
#include <thread>
#include <unistd.h>
#include <sys/types.h>
#include <string>

using namespace std;

void *test(void *arg)
{
    string name = (char *)arg;
    int count = 5;
    while (count--) // 5秒后会结束
    {
        cout << "new thread's name:" << name << endl;
        std::cout << "I'm new thread, pid: " << " new thread id pthread_self:" << pthread_self() << endl;
        sleep(1);
    }
    pthread_exit((void *)123);
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, test, (void *)"thread_001");
    // 这里是主线程
    void *ret_newthread = nullptr;
    int n = pthread_join(tid, &ret_newthread);
    cout << (long long)ret_newthread << endl; // 这里我们的云服务器是64位,那void* 就是8个字节,所以强转为long long
    return 0;
}
pthread_cancel()函数

pthread_cancel() 函数是 POSIX 线程库中的一个函数,用于向指定线程发送取消请求,请求该线程终止执行

函数原型:

代码语言:javascript复制
int pthread_cancel(pthread_t thread);

参数:

  • thread:要取消的线程的标识符(通常是通过 pthread_create() 创建的线程返回的 pthread_t tid)。

功能:

  • pthread_cancel() 函数用于向指定线程发送取消请求,请求该线程终止执行。
  • 调用 pthread_cancel() 函数并不会立即终止目标线程的执行,而是发送一个取消请求,目标线程可以在适当的时候响应取消请求并终止执行。
  • 目标线程可以通过设置取消状态和取消类型来决定如何处理取消请求,具体处理方式可以通过 pthread_setcancelstate()pthread_setcanceltype() 函数来设置。

返回值:

  • 如果成功发送取消请求,pthread_cancel() 函数返回值为0。
  • 如果出现错误,返回值为对应的错误码。

注意事项:

  • pthread_cancel() 函数并不保证目标线程会立即终止执行,目标线程需要在适当的时候检查取消请求并做出响应。
  • 取消请求是异步的,目标线程需要在适当的时候检查取消状态以响应取消请求。
  • 可以通过 pthread_testcancel() 函数来显式检查取消请求并做出响应。

2.5线程分离

  • 线程默认都是joinable,需要等待的。上述我们等待都是阻塞等待
  • 其实也是有非堵塞等待的——不关注新线程的返回结果,只要求能完成相应的任务即可,我们可以把该线程设为分离状态:线程退出时自动释放资源
  • 被分离的线程不能再join了,只是主线程不需要再等待了,还是要共享资源的。当主线程结束,新线程也结束;新线程出现错误,进程也会结束
  • 所以我们一般还是希望主线程是最后结束的,一般我们也是主线程是死循环一直执行(常驻进程)时使用线程分离

pthread_detach 是一个在 POSIX 线程编程(也称为 Pthreads)中常用的函数,用于改变一个线程的属性,使得当该线程终止时,其相关的资源(如线程栈)能够被系统自动回收,而不需要其他线程显式地调用 pthread_join

基本概念

在 Pthreads 中,线程有两种状态:可连接的(joinable)和分离的(detached)。

  • 可连接的(joinable):默认情况下,新创建的线程是可连接的。这意味着,当线程终止时,它的资源不会被立即释放。相反,它们会保持“悬挂”状态,直到另一个线程调用 pthread_join 来回收这些资源。这允许我们访问线程的退出状态或返回值。
  • 分离的(detached):当线程被设置为分离状态时,一旦它终止,其资源就会被系统自动回收,而不需要其他线程调用 pthread_join
pthread_detach 函数

pthread_detach 函数的原型如下:

代码语言:javascript复制
int pthread_detach(pthread_t thread);
  • thread:这是你想要改变其状态的线程的线程标识符(ID)。

返回值:

如果成功,返回 0;如果失败,返回错误码。

注意事项

  • 一旦线程被设置为分离状态,你就不能再调用 pthread_join 来回收它的资源了。如果你尝试这样做,pthread_join 会返回一个错误。
  • 如果线程已经终止(并且是可连接的),那么调用 pthread_detach 将会立即释放其资源,就像你已经调用了 pthread_join 一样。但是,请注意,你将无法获取线程的退出状态或返回值。
  • 如果你不关心线程的退出状态或返回值,或者你确定没有其他线程会调用 pthread_join 来回收该线程的资源,那么将线程设置为分离状态通常是一个好主意,因为它可以避免资源泄漏和相关的管理开销。

3.重要结论

线程的优点:

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

与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多:

而且线程间切换的上下文也少一点:地址空间、页表都是一份(当然,这不是大头)

计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现:

这不意味着线程越多越好,因为线程多了后,线程切换的消耗也会大大增加,一般都是CPU有几核就用几个


线程缺点:

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

线程异常:

  • 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
  • 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出

线程独占的:

  • 线程的硬件上下文(CPU寄存器的值)(调度时会使用)
  • 线程的独立栈空间:每个线程有自己的栈空间

线程共享:

  • 代码和全局数据
  • 进程文件描述符表

4.多线程创建

  1. 我们要注意,不能把一个公共资源传给多个线程。使用new
  2. 我们可以给线程函数传参,传任何类型都行
代码语言:javascript复制
#include <iostream>
#include <thread>
#include <unistd.h>
#include <sys/types.h>
#include <string>
#include <cstdlib>
#include <vector>

using namespace std;

class Task
{
public:
    Task()
    {
    }
    void SetData(int x, int y)
    {
        _datax = x;
        _datay = y;
    }

    int Excute()
    {
        return _datax   _datay;
    }
    ~Task()
    {
    }

private:
    int _datax;
    int _datay;
};

class ThreadData : public Task
{
public:
    ThreadData(int x, int y, const string &threadname) : _threadname(threadname)
    {
        _t.SetData(x, y);
    }

    string GetThreadname()
    {
        return _threadname;
    }
    int RunTask()
    {
        return _t.Excute();
    }

private:
    string _threadname;
    Task _t;
};

class Result
{
public:
    Result()
    {
    }
    ~Result()
    {
    }
    void GetResult(int result, const std::string &threadname)
    {
        _result = result;
        _threadname = threadname;
    }
    void PrintResult()
    {
        std::cout << _threadname << ": " << _result << std::endl;
    }

private:
    int _result;
    string _threadname;
};

void *threadTask(void *args)
{
    ThreadData *td = static_cast<ThreadData *>(args);
    string name = td->GetThreadname();
   
    int result = td->RunTask();

    Result *res = new Result(); // new出来的方便返回
    res->GetResult(result, name);
    sleep(2);
    delete td;

    return res;
}

const int thread_num = 4; // 我们创建四个
int main()
{
    vector<pthread_t> threads;
    for (int i = 0; i < thread_num; i  )
    {
        char thread_name[20];
        snprintf(thread_name, sizeof(thread_name), "thread-00%d", i   1); // 新线程名字写好

        ThreadData *td = new ThreadData(1, 2, thread_name); // new好一个自定义变量后
        pthread_t tid;
        pthread_create(&tid, nullptr, threadTask, td); // 我们让线程执行函数接受一个类变量

        threads.push_back(tid); // 把4个线程的tid放进vector里面
    }
    // 到了这里,开始等待
    vector<Result *> result;
    void *ret = nullptr;
    for (auto &e : threads)
    {
        pthread_join(e, &ret); // 返回结果已经到ret里了
        result.push_back((Result *)ret);
    } // 到这里就说明已经push完了
    // 下面直接开始遍历打印就行
    for (auto &res : result)
    {
        res->PrintResult();
        delete res;
    }

    return 0;
}

5.c 11里的多线程VS系统的

代码语言:javascript复制
#include <iostream>
#include <thread> // C  里的库
#include <unistd.h>

using namespace std;

void threadrun(int num)
{
    while (num)
    {
        cout << "I am a thread, num: " << num << endl;
        sleep(1);
    }
}
int main()
{
    std::thread t1(threadrun, 10);
    std::thread t2(threadrun, 10);
    std::thread t3(threadrun, 10);
    std::thread t4(threadrun, 10);

    while (true)
    {
        std::cout << "I am a main thread " << std::endl;
        sleep(1);
    }

    t1.join();
    t2.join();
    t3.join();
    t4.join();

    return 0;
}

如果我们直接使用

代码语言:javascript复制
g   test.cc -std=c  11 

但是会发现: undefined reference to `pthread_create’ 这不是我们Linux里的原生库吗

结论:C 11的多线程,是对原生线程的封装

  1. 为什么要封装呢? 还是保证语言的可跨平台性:上述代码我们在vs下也是依然能运行,但是我们上次使用的Linux原生库里的pthread_create就不能再vs下面运行
  2. 其他语言呢? 虽然一些其他语言(如Java、Python等)提供了自己的线程管理库或框架,但这些库或框架通常也是基于底层操作系统的原生线程API进行封装的。

尝试模拟封装一下

代码语言:javascript复制
#ifndef __THREAD_HPP__
#define __THREAD_HPP__
#include <iostream>
#include <string>
#include <unistd.h>
#include <functional>
#include <vector>
#include <pthread.h> //Linux的原生线程库

namespace MyThread // 模拟c  的线程库,也对Linux原生库进行封装
{
    template <typename T>
    using func_t = std::function<void(T)>; // 把返回值为空,参数是T的函数对象包装
    // 就相当于typedef std::function<void(const T&)> func_t;

    template <typename T>
    class Thread
    {
    public:
        Thread(func_t<T> func, T data, const std::string &name = "none-name") // 构造函数
            : _fun(func), _data(data), _thread_name(name), _stop(true)
        {
        }
        ~Thread() {}

        void Excute()
        {
            _fun(_data);
        }

        static void *runfunction(void *args) // 这个函数是线程执行的函数,我们的目的是运行传过来的函数
        {
            Thread<T> *self = static_cast<Thread<T> *>(args); // 把void* 强转为该类的类型
            self->Excute();
            // self->_fun(_data);
            return nullptr;
        }

        bool Start() // 线程的启动函数
        {
            int n = pthread_create(&_tid, nullptr, runfunction, this);
            if (!n)
            {
                _stop = false; // 说明没有stop
                return true;
            }
            else
            {
                return false;
            }
        }

        void Detach() // 线程分离(非堵塞等待)
        {
            if (!_stop)
            {
                pthread_detach(_tid);
            }
        }
        void Join() // 线程等待
        {
            if (!_stop) // 当线程没有停止时,才能等
            {
                pthread_join(_tid, nullptr);
            }
        }
        std::string name()
        {
            return _thread_name;
        }
        void Stop()
        {
            _stop = true;
        }

    private:
        pthread_t _tid;
        std::string _thread_name; // 线程的名字
        func_t<T> _fun;           // 要线程执行的函数
        T _data;                  // 函数使用的数据类型
        bool _stop;               // 线程是否停止
    };
}

#endif // 与前两行形成格式

今天就到这里啦,马上就把写的笔记一个一个发出来了

0 人点赞