多线程编程初探:掌握基本概念与核心原理

news2024/12/29 6:23:34

目录

1 初识线程

1.1 线程的由来

1.2 线程的产生

1.3 进程 VS 线程

1.4 关于系统内部关于线程和进程的资源调度问题

2 页表、虚拟地址和物理地址

2.1 对物理地址的描述

2.2 对于页表设计的解析

3 线程的控制

 3.1 进程创建

 3.1.1 pthread_create

 3.2 线程退出

3.2.1 主线程退出,其余进程全部都要结束

3.2.2 pthread_exit() 线程退出函数

3.2.3 pthread_cancel() 线程取消

3.2.4 线程异常退出

3.3 进程等待

3.3.1 pthread_join()

 3.4 线程分离 

3.4.1 在创建线程时设置分离属性:

3.4.2 在创建线程后设置分离属性(使用pthread_detach函数)

3.5 线程之间通信

4 线程用户层面的线程id和Linux底层的轻量级进程的id

4.1 线程用户层面的线程id

4.1.1 pthread_self()

 4.2 Linux底层的轻量级进程的id

5 多线程的创建,以及实现多线程任务

6 进程与线程的深度比较

7.1 线程的优缺点 

7.1  线程的优点:

7.2 线程的缺点:

7.3 面试题

8 C++11封装的线程库

9 线程库的理解

9.1 用户层面的线程id(底层剖析)

9.2 线程pthread的理解

9.3 线程局部存储(TLS)


1 初识线程

线程是进程内部的一个执行分支,是CPU调度的基本单元

1.1 线程的由来

就当论前面那个线程的概念,我相信所有人都是一脸懵逼。

就是我们在进程当中运行程序的时候都是单执行流往下运行的,如果要多执行流往下运行,就需要引入多进程。

 

但是呢,多进程有一个缺点,就是创建子进程的时候需要,创建PCB和进程地址空间,页表等等内核数据结构比较浪费资源。

就是多进程成本比较高,我们需要一个低成本的方式来实现一份代码并行。

1.2 线程的产生

首先,我们就要对线程进行先描述,在组织。这样就有了线程控制块PCB概念的产生。

这样一来,我们就需要对线程重新设计一套适用于线程的系统调用、数据结构、和调度算法等。

又因为线程和进程高度相似,所以这里衍生出了两种对于线程的设计模式。

操作系统这么学科当中是有明确TCB这个概念的,但在具体的系统的实现过程中,Windows采用了对于多线程另起炉灶的想法,重新设计了TCB。而Linux中则是对于多线程这方面的底层设计,则复用了PCB的设计。

这两种方法我认为是Linux更好,这样就减少了维护成本。

这样就是三个线程在同一个进程当中,将正文代码分成三份,并行向上执行。

1.3 进程 VS 线程

以前讲述的进程就是目前理解的进程的特殊情况。

站在内核的角度上来看:进程就是承担系统资源调度的基本实体


1.4 关于系统内部关于线程和进程的资源调度问题

首先,我们明确一点,就是在Linux下线程复用了进程的代码。那么在内核调用task_struct(都是执行流)不会有任何区别。

 还就是要明确一点就是:PCB只是一个进程控制块,他不是整个进程,进程 = 内核数据结构 + 进程代码和数据。

这里的轻量级进程就是指着一个进程控制块+进程地址空间+页表+内存上的代码


2 页表、虚拟地址和物理地址

2.1 对物理地址的描述

在磁盘内容当中,每个inode的数据块都是4KB的,所以磁盘上的内容加载进入内存也是以4KB为基本单位的。

所以,我们之前提到的任何关于内存的操作都是以4KB为基本单位的,比如:new申请空间,子进程修改代码和数据发生的写时拷贝都是以4KB为基本单位的。 

2.2 对于页表设计的解析

首先,我们之前对于页表都没有一个明确的介绍,可能会让大家认为,页表就是一个普通的KV结构,我们稍微想一想就知道页表肯定不会是一个简单的KV结构,因为这样的页表需要的内存空间实在是太大了。

所以,Linux在设计的时候就对页表进行了类似于多级索引式的设计。

给不同的线程分配不同的的区域本质就是为了让不同的线程,各自看到所有页表的子集。

3 线程的控制

 3.1 进程创建

 3.1.1 pthread_create

pthread_create 是 POSIX 线程(也称为 pthreads)库中用于创建一个新线程的函数。它是多线程编程中的一个关键函数,允许你在一个进程中并发地执行多个线程

参数解释

  • pthread_t *thread:这是一个指向 pthread_t 类型变量的指针,用于存储新创建线程的线程标识符。当你调用 pthread_create 时,这个变量将被赋值为新线程的 ID。

  • const pthread_attr_t *attr:这是一个指向 pthread_attr_t 结构体的指针,用于指定线程的属性。如果你不需要设置特殊的线程属性,可以传递 NULL,表示使用默认属性。

  • void *(*start_routine) (void *):这是一个指向函数的指针,该函数是新线程的起始执行点。这个函数必须返回一个 void * 类型的值,并接受一个 void * 类型的参数。这个参数允许你将数据传递给新线程。

  • void *arg:这是传递给 start_routine 函数的参数。你可以通过这个参数将任何类型的数据传递给新线程,只需确保在 start_routine 函数中正确地解释和转换这个参数。

返回值

  • 如果函数成功,pthread_create 将返回 0
  • 如果函数失败,它将返回一个非零的错误码,表示创建线程时发生的错误。

 3.2 线程退出

3.2.1 主线程退出,其余进程全部都要结束

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

std::string ToHex(pthread_t tid)
{
    char id[64];
    snprintf(id, sizeof(id), "0x%lx", tid);
    return id;
}

void *handler(void *args)
{
    while (1)
    {
        std::cout << "I am new pthread , pid : " << getpid() << std::endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, handler, nullptr);

    int cnt = 5;
    while (cnt)
    {
        std::cout << "I am main pthread , pid : " << getpid() << " pthread id : " << ToHex(tid) << std::endl;
        sleep(1);
        cnt--;
    }

     std::cout << "I am main pthread ,is quited"<<  std::endl;
    return 0;
}


3.2.2 pthread_exit() 线程退出函数

参数

  • retval:一个指向任意类型数据的指针,该数据将被返回给调用 pthread_join() 的线程。这个返回值可以通过类型转换来匹配任何所需的数据类型。如果不需要返回值,可以将 retval 设置为 NULL


3.2.3 pthread_cancel() 线程取消

pthread_cancel是POSIX线程(pthread)库中用于取消指定线程执行的函数。 

参数说明

  • thread:指定要取消的线程的标识符(pthread_t类型)。

返回值

  • 成功时返回0。
  • 失败时返回非0值,通常用于指示错误类型。

工作机制

  • pthread_cancel函数发送一个取消请求给指定的线程,但并不会立即终止该线程的执行。
  • 线程在接收到取消请求后,会继续运行,直到到达某个取消点(Cancellation Point)。取消点是线程检查是否被取消并按照请求进行动作的一个位置。(这就类似于进程退出,处理信号,防止突然的退出,导致不可预料的错误)
  • 线程可以通过调用pthread_testcancel函数来主动检查是否存在取消请求,并在发现请求时执行相应的处理(如退出线程)。


3.2.4 线程异常退出

1. 代码跑完,结果对 

2. 代码跑完,结果不对 

3. 出异常了 --- 重点 --- 多线程中,任何一个线程出现异常(div 0, 野指针), 都会导致整个进程退出! ---- 多线程代码往往健壮性不好

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

std::string ToHex(pthread_t tid)
{
    char id[64];
    snprintf(id, sizeof(id), "0x%lx", tid);
    return id;
}
// 线程退出
// // 1. 代码跑完,结果对
// // 2. 代码跑完,结果不对
// // 3. 出异常了 --- 重点 --- 多线程中,任何一个线程出现异常(div 0, 野指针), 都会导致整个进程退出! ---- 多线程代码往往健壮性不好

void *handler(void *args)
{
    int cnt = 10;
    while (cnt)
    {
        std::cout << "I am new pthread , pid : " << getpid() << std::endl;
        sleep(1);
        cnt--;

        int *p =nullptr;
        *p=100;
    }
    pthread_exit((void *)1234);
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, handler, nullptr);

    int cnt = 5;
    while (1)
    {
        std::cout << "I am main pthread , pid : " << getpid() << " pthread id : " << ToHex(tid) << std::endl;
        sleep(1);
        cnt--;
    }

    // void *nums;
    // pthread_join(tid, &nums);
    // std::cout << "I am main pthread ,is quited" << "exit nums "<< (long long)nums <<std::endl;
    return 0;
}

新线程出现了异常(除零错误),无论是新线程还是主线程的死循环都退出了 


3.3 进程等待

3.3.1 pthread_join()

pthread_join 是 POSIX 线程(pthread)库中用于等待一个特定的线程终止的函数。当你创建一个线程后,主线程(或其他线程)可能需要等待该线程完成其任务后再继续执行。

参数

  • thread:要等待的线程的标识符,通常是通过 pthread_create 函数获得的。
  • retval:一个指向指针的指针,用于接收被等待线程的返回值。如果不需要这个返回值,可以将其设置为 NULL。被等待线程的返回值应该是一个 void* 类型的指针,这允许线程返回任何类型的数据(通过类型转换)。如果 retval 不是 NULL,那么 pthread_join 会将 retval 所指向的位置设置为被等待线程的返回值。

返回值

  • 成功时,pthread_join 返回 0
  • 失败时,返回一个错误码。常见的错误码包括 ESRCH(无此线程)、EINVAL(线程不是可连接的,或者 thread 不表示一个线程),以及 EDEADLK(检测到死锁)。

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

std::string ToHex(pthread_t tid)
{
    char id[64];
    snprintf(id, sizeof(id), "0x%lx", tid);
    return id;
}

void *handler(void *args)
{
    int cnt = 10;
    while (cnt)
    {
        std::cout << "I am new pthread , pid : " << getpid() << std::endl;
        sleep(1);
        cnt--;
    }
    pthread_exit((void *)1234);
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, handler, nullptr);

    int cnt = 5;
    while (cnt)
    {
        std::cout << "I am main pthread , pid : " << getpid() << " pthread id : " << ToHex(tid) << std::endl;
        sleep(1);
        cnt--;
    }
    void *nums;
    pthread_join(tid, &nums);
    std::cout << "I am main pthread ,is quited" << "exit nums "<< (long long)nums <<std::endl;
    return 0;
}


 3.4 线程分离 

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

简单来讲就是,把线程分离出去,不在需要主线程进行等待了。

线程分离可以通过以下两种方法实现:

3.4.1 在创建线程时设置分离属性

使用pthread_create函数创建线程时,可以通过该函数的第二个参数(线程属性对象)来设置线程的分离属性。

3.4.2 在创建线程后设置分离属性(使用pthread_detach函数)

  • 参数thread是要设置为脱离状态的线程的ID。
  • 返回值:成功时返回0;失败时返回一个非零错误码。
#include <iostream>
#include <string>
#include <unistd.h>
#include <functional>
#include <pthread.h>
#include <string.h>
#include <error.h>

void *pthreadrun(void *args)
{
    pthread_detach(pthread_self());
    int cnt = 5;
    while (cnt)
    {
        const char *name = static_cast<const char *>(args);
        std::cout << "I am " << name << std::endl;
        cnt--;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    const char name[64] = "new pthread-1";

    pthread_t tid;
    pthread_create(&tid, nullptr, pthreadrun, (void *)name);
    std::cout << "I am main pthread , begin wait" << std::endl;
    
    int n = pthread_join(tid, nullptr);

    std::cout << "I am main pthread" <<"n : "<< n << strerror(n) << std::endl;
    return 0;
}


3.5 线程之间通信

// 同一个进程内的线程,大部分资源都是共享的. 地址空间是共享的!

#include <iostream>
#include <pthread.h>
#include <unistd.h>
// 同一个进程内的线程,大部分资源都是共享的. 地址空间是共享的!
int g_val = 100;

std::string ToHex(pthread_t tid)
{
    char id[64];
    snprintf(id, sizeof(id), "0x%lx", tid);
    return id;
}
// 线程退出
// // 1. 代码跑完,结果对
// // 2. 代码跑完,结果不对
// // 3. 出异常了 --- 重点 --- 多线程中,任何一个线程出现异常(div 0, 野指针), 都会导致整个进程退出! ---- 多线程代码往往健壮性不好

void *handler(void *args)
{
    int cnt = 10;
    while (cnt)
    {
        std::cout << "I am new pthread , pid : " << getpid() << "  g_val : "<<g_val << std::endl;
        sleep(1);
        cnt--;

        // int *p =nullptr;
        // *p=100;
    }
    pthread_exit((void *)1234);
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, handler, nullptr);

    int cnt = 5;
    while (1)
    {
        g_val++;
        std::cout << "I am main pthread , pid : " << getpid() << " pthread id : " << ToHex(tid) << "g_val :  "<<g_val<< std::endl;
        sleep(1);
        cnt--;
    }

    // void *nums;
    // pthread_join(tid, &nums);
    // std::cout << "I am main pthread ,is quited" << "exit nums "<< (long long)nums <<std::endl;
    return 0;
}


4 线程用户层面的线程id和Linux底层的轻量级进程的id

4.1 线程用户层面的线程id

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

std::string ToHex(pthread_t tid)
{
    char id[64];
    snprintf(id, sizeof(id), "0x%lx", tid);
    return id;
}

void *handler(void *args)
{
    while (1)
    {
        std::cout << "I am new pthread , pid : " << getpid() << std::endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, handler, nullptr);

    while (1)
    {
        std::cout << "I am main pthread , pid : " << getpid() << " pthread id : " << ToHex(tid) << std::endl;
        sleep(1);
    }
    return 0;
}

4.1.1 pthread_self()

 

功能

pthread_self 函数不需要任何参数,它返回调用线程的线程ID。这个线程ID是一个 pthread_t 类型的值,通常用于线程的管理、线程间的通信和同步等操作。

返回值

pthread_self 总是成功返回调用线程的线程ID。这个ID在线程的整个生命周期内是唯一的,但如果有多个线程,并且有一个线程完成,那么该ID就可以被重用。因此,对于所有正在运行的线程,ID在当前时刻是唯一的。


 4.2 Linux底层的轻量级进程的id

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

using namespace std;

void *newthreadrun(void *args)
{

    while (1)
    {
        cout << "I am new pthread" << endl;
        sleep(1);
    }
    
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, newthreadrun, nullptr);

    while (1)
    {
        cout << "I am main pthread" << endl;
        sleep(1);
    }
    return 0;
}

这里我们看到了同一个pid(同一个进程)下的两个线程(LWP不同)。

所以,操作系统在调度的时候,是使用哪一个id进行调度呢?-- LWP


5 多线程的创建,以及实现多线程任务

#include <iostream>
#include <string>
#include <vector>
#include <cstdio>
#include <unistd.h>
#include <cstdlib>
#include <pthread.h> // 原生线程库的头文件

const int threadnum = 5;

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 std::string &threadname):_threadname(threadname)
    {
        _t.SetData(x, y);
    }
    std::string threadname()
    {
        return _threadname;
    }
    int run()
    {
        return _t.Excute();
    }
private:
    std::string _threadname;
    Task _t;
};

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

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

    std::string name = td->threadname();

    Result *res = new Result();
    int result = td->run();

    res->SetResult(result, name);

    // std::cout << name << "run result : " << result << std::endl;
    delete td;

    sleep(2);
    return res;
  
}
// 1. 多线程创建
// 2. 线程传参和返回值,我们可以传递级别信息,也可以传递其他对象(包括你自己定义的!)

int main()
{
    std::vector<pthread_t> threads;
    for (int i = 0; i < threadnum; i++)
    {
        char threadname[64];
        snprintf(threadname, 64, "Thread-%d", i + 1);
        ThreadData *td = new ThreadData(10, 20, threadname);

        pthread_t tid;
        pthread_create(&tid, nullptr, handlerTask, td);
        threads.push_back(tid);
    }
    std::vector<Result*> result_set;
    void *ret = nullptr;
    for (auto &tid : threads)
    {
        pthread_join(tid, &ret);
        result_set.push_back((Result*)ret);
    }

    for(auto & res : result_set)
    {
        res->Print();
        delete res;
    }
}

6 进程与线程的深度比较

进程是资源分配的基本单位线程是调度的基本单位

线程共享进程数据,但也拥有自己的一部分数据:

  1. 线程ID
  2. 一组寄存器栈
  3. errno
  4. 信号屏蔽字调度优先级

私有:线程的硬件上下文(CPU寄存器上的值)(调度)

        线程的独立栈结构(常规运行) 

共享:代码和全局数据

        进程文件描述符表

进程的多个线程共享 同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:

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

 进程与线程的关系如下:

7.1 线程的优缺点 

7.1  线程的优点:

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

7.2 线程的缺点:

性能损失

  • 一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型
  • 线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的
  • 同步和调度开销,而可用的资源不变。

健壮性降低

  • 编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。

缺乏访问控制

  • 进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。编程难度提高
  • 编写与调试一个多线程程序比单线程程序困难得多

7.3 面试题

问:为什么与进程相比,线程切换操作系统要做的任务少很多?

1.上下文切换(非主要原因)

切换进程的时候,加载进入CPU的上下文数据全都要重新加载,因为进程地址空间,页表。。都是独立的;而线程就不需要全部重新加载,只需要重新加载一部分。

2.局部性原理 (主要问题)

CPU在调度进程的时候,当一条代码被执行的时候,CPU内部的cache就会选择性的缓存被执行代码的附近50行代码,期待这些代码能够被执行,这样就能提高效率,但是,下一条代码没有在缓存中被调度,CPU就会根据算法,删除一部分,重新写入一部分。

这样看来,如果进程切换,代码这些全部都重新加载进入内存,所以这些缓存全部都要被清除。

但是线程的话,还是执行同一份代码,可能只不过不是同一部分,但是缓存中的代码就不需要全部被清楚,或者只需要重新重新加载一部分。 

8 C++11封装的线程库

#include <iostream>
#include <thread>
#include <unistd.h>

using namespace std;

void threadrun(int args)
{
    while (true)
    {
        cout << "I am new pthread" << endl;
        sleep(1);
    }
}
int main()
{
    thread(threadrun, 10);

    return 0;
}

该代码没有运用任何C原生线程库里的函数,使用的都是C++库里的函数,为什么编译的时候产生了错误 

这个错误说明了,代码编译的时候,还需要链接原生线程库,这是为什么? 

这说明了C++11,在Linux环境下对于线程的操作,都是封装的Linux下的原生线程库。

这是为了代码的跨平台性。

那么C++在Windows下,还是封装的Linux的原生线程库吗?肯定不是了,封装的是Windows下的原生线程库了。应该是相对平台进行判断,然后选择同一份代码的不同部分使用。

那么其他语言呢?我们只需要明白一点,Linux原生线程库是,Linux底层实现多线程的唯一方法。


9 线程库的理解

9.1 用户层面的线程id(底层剖析)

我们能很明显的看到,Linux底层封装的轻量级进程的编号和原生线程库层面封装的线程编号完全不是一个东西。


9.2 线程pthread的理解

首先,要使用线程就得,先让线程库加载进入内存,然后映射到进程地址空间的共享区内。 

一个进程当中会有很多线程,那么我们就需要对线程进行管理。但是在Linux操作系统方面底层没有线程的概念,只有轻量级线程的概念,所以Linux无法维护线程。Linux上的线程的概念是有线程库来提供的,所以线程的维护就应该交给线程库。

关于这方面就直接说了:用户层面的线程编号:struct_pthread的开头地址。


9.3 线程局部存储(TLS)

  1. 线程局部存储:是一种机制,允许每个线程拥有自己变量的副本,这些变量在每个线程中独立存在、互不影响。这种机制确保了线程数据的独立性,从而避免了全局变量或静态变量在并发环境下竞态条件和数据不一致的问题。(线程局部存储只能存储自定义类型)
  2. 线程局部存储的优点:数据隔离、减少了同步开销、提高了性能。
  3. __thread关键字:用于声明线程局部存储变量,使用__thread关键字声明的变量在每个线程中都有一个独立的副本,这些副本互不影响,有助于避免线程间的竞态条件或数据不一致问题,提高线程安全性。

__thread 数据类型 变量名 ;

#include <iostream>
#include <thread>
#include <unistd.h>

using namespace std;

__thread int g_val=100;

void* pthreadrun1(void* args)
{
  while(true)
    {
        cout<<"The new pthread-1 tid: "<<pthread_self()<<"g_val: "<<g_val<<endl;
        sleep(1);
        g_val++;
    }
}

void* pthreadrun2(void* args)
{
  while(true)
    {
        cout<<"The new pthread-2 tid: "<<pthread_self()<<"g_val: "<<g_val<<endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid1 ;
    pthread_create(&tid1,nullptr,pthreadrun1,nullptr);

    pthread_t tid2 ;
    pthread_create(&tid2,nullptr,pthreadrun2,nullptr);

    while(1)
    {

    }

    return 0;
}

10 自己利用原生库简单的封装成C++11的线程库

#ifndef __THREAD_HPP__
#define __THREAD_HPP__

#include <iostream>
#include <string>
#include <unistd.h>
#include <functional>
#include <pthread.h>

namespace ThreadModule
{
    template<typename T>
    using func_t = std::function<void(T)>;
    // typedef std::function<void(const T&)> func_t;

    template<typename T>
    class Thread
    {
    public:
        void Excute()
        {
            _func(_data);
        }
    public:
        Thread(func_t<T> func, T data, const std::string &name="none-name")
            : _func(func)
            , _data(data)
            , _threadname(name)
            , _stop(true)
        {}

        static void *threadroutine(void *args) // 类成员函数,形参是有this指针的!!
        {
            Thread<T> *self = static_cast<Thread<T> *>(args);
            self->Excute();
            return nullptr;
        }

        bool Start()
        {
            int n = pthread_create(&_tid, nullptr, threadroutine, this);
            if(!n)
            {
                _stop = false;
                return true;
            }
            else
            {
                return false;
            }
        }

        void Detach()
        {
            if(!_stop)
            {
                pthread_detach(_tid);
            }
        }

        void Join()
        {
            if(!_stop)
            {
                pthread_join(_tid, nullptr);
            }
        }

        std::string name()
        {
            return _threadname;
        }
        
        void Stop()
        {
            _stop = true;
        }
        ~Thread() {}

    private:
        pthread_t _tid;
        std::string _threadname;
        T _data;  // 为了让所有的线程访问同一个全局变量
        func_t<T> _func;
        bool _stop;
    };
} // namespace ThreadModule

#endif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2267310.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电力场景配网缺陷系列之销钉缺失检测数据集VOC+YOLO格式3095张2类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;3095 标注数量(xml文件个数)&#xff1a;3095 标注数量(txt文件个数)&#xff1a;3095 …

2024国城杯 Web

这四道题目Jasper大佬都做了镜像可以直接拉取进行复现 https://jaspersec.top/2024/12/16/0x12%20%E5%9B%BD%E5%9F%8E%E6%9D%AF2024%20writeup%20with%20docker/ n0ob_un4er 这道题没有复现成功, 不知道为啥上传了文件, 也在 /tmp目录下生成了sess_PHPSESSID的文件, 但是就是…

MLLM学习过程

视频理解 SALOVA: Segment-Augmented Long Video Assistant for Targeted Retrieval and Routing in Long-Form Video Analysis 主要是用于增强对于长视频的理解。主要是讲视频进行剪切之后&#xff0c;首先判断每个剪切视频短对于文字的关联程度&#xff0c;并且将关联程度高…

MATLAB用find函数结合all,any函数高效解决问题

如本节中最后提到的问题&#xff0c;我们输出后还需要判断&#xff0c;不是特别的一目了然&#xff0c;这时候我们可以再加上 f i n d find find函数直接标记序号并输出。首先我们先来了解 f i n d find find的用法&#xff0c; f i n d ( a ) find(a) find(a)表示将矩阵或向量…

【最新】西陆房产系统源码+uniapp全开源+环境教程

一.介绍 西陆房产管理系统&#xff0c;支持小程序、H5、APP&#xff1b;包含房客、房东(高级授权)、经纪人(高级授权)三种身份。核心功能有&#xff1a;新盘销售、房屋租赁、地图找房、房源代理(高级授权)、在线签约(高级授权)、电子合同(高级授权)、客户CRM跟进(高级授权)、经…

【Halcon】例程讲解:基于形状匹配与OCR的多图像处理(附图像、程序下载链接)

1. 开发需求 在参考图像中定义感兴趣区域&#xff08;ROI&#xff09;&#xff0c;用于形状匹配和文本识别。通过形状匹配找到图像中的目标对象位置。对齐多幅输入图像&#xff0c;使其与参考图像保持一致。在对齐后的图像上进行OCR识别&#xff0c;提取文本和数字信息。以循环…

【UE5.3.2】生成vs工程并rider打开

Rider是跨平台的,UE也是,当前现在windows上测试首先安装ue5.3.2 会自动有右键的菜单: windows上,右键,生成vs工程 生成的结果 sln默认是vs打开的,我的是vs2022,可以open with 选择 rider :Rider 会弹出 RiderLink是什么插

力扣刷题:单链表OJ篇(下)

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 目录 1.环形链表&#xff08;1&#xff09;题目描述…

如何在idea中搭建SpringBoot项目

如何在idea中快速搭建SpringBoot项目 目录 如何在idea中快速搭建SpringBoot项目前言一、环境准备&#xff1a;搭建前的精心布局 1.下载jdk &#xff08;1&#xff09;安装JDK&#xff1a;&#xff08;2&#xff09;运行安装程序&#xff1a;&#xff08;3&#xff09;设置安装…

(源码)校园闲置交易管理系统 P10111 计算机毕业设计

项目说明 本号所发布的项目均由我部署运行验证&#xff0c;可保证项目系统正常运行&#xff0c;以及提供完整源码。 如需要远程部署/定制/讲解系统&#xff0c;可以联系我。定制项目未经同意不会上传&#xff01; 项目源码获取方式放在文章末尾处 注&#xff1a;项目仅供学…

ID读卡器UDP协议Delphi7小程序开发

如下是小程序主页面&#xff1a; 代码如下&#xff1a; function isrightint(textls:string):boolean;stdcall; begintryif(strtoint(textls) 0) thenbeginend;result : True;exceptresult : False;exit;end; end; procedure TForm1.Button9Click(Sender: TObject); varsendbu…

内部类(1)

大家好&#xff0c;今天我们来学习一下内部类&#xff0c;内部类也是封装的体现&#xff0c;那么我们便来看看它的内容吧。 9、内部类 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么这个内部的完整结构最好使用…

永磁同步电机无速度算法--自适应全阶滑模观测器

一、原理介绍 提出了一种改进型全阶滑模观测器的无位置传感器控制方法。首先&#xff0c;以准符号函数作为滑模控制函数&#xff0c;达到削弱抖振和提高反电动势估计性能的目的&#xff1b;其次&#xff0c;设计与电机转速相关的自适应滑模增益&#xff0c;以避免电机转速变化…

微软远程桌面APP怎么用

微软远程桌面&#xff08;Remote Desktop&#xff09;客户端&#xff08;RD Client&#xff09;是一款由微软开发的应用程序&#xff0c;允许用户通过网络连接远程访问和控制另一台计算机。同时&#xff0c;微软远程桌面RD Client支持多种设备和操作系统&#xff0c;包括Window…

phidata快速开始

文章目录 什么是phidata主要特点 安装官方demo创建一个 Web 搜索代理 PhiData开发workflow应用ToolsAgent UI 什么是phidata github: https://github.com/phidatahq/phidata 官方文档&#xff1a;https://docs.phidata.com/introduction Phidata is a framework for building…

考研互学互助系统|Java|SSM|VUE| 前后端分离

【技术栈】 1⃣️&#xff1a;架构: B/S、MVC 2⃣️&#xff1a;系统环境&#xff1a;Windowsh/Mac 3⃣️&#xff1a;开发环境&#xff1a;IDEA、JDK1.8、Maven、Mysql5.7 4⃣️&#xff1a;技术栈&#xff1a;Java、Mysql、SSM、Mybatis-Plus、VUE、jquery,html 5⃣️数据库…

王佩丰24节Excel学习笔记——第二十三讲:饼图美化与PPT图表

【以 Excel2010 系列学习&#xff0c;用 Office LTSC 专业增强版 2021 实践】 【本章技巧】 主次坐标柱形避让&#xff0c;做成左右两条柱型图的显示&#xff1b;饼图最多只能做2层&#xff0c;超过2层要使用环型图做&#xff0c;饼图与环形图结合可以做多层&#xff0c;注意&a…

从虚拟到现实:AI与AR/VR技术如何改变体验经济?

引言&#xff1a;体验经济的崛起 在当今消费环境中&#xff0c;产品与服务早已不再是市场竞争的唯一焦点&#xff0c;能够提供深刻感知和独特体验的品牌&#xff0c;往往更能赢得消费者的青睐。这种转变标志着体验经济的崛起。体验经济不仅仅是简单的买卖行为&#xff0c;而是通…

最新SQL Server 2022保姆级安装教程【附安装包】

目录 一、安装包下载&#xff1a; 下载链接&#xff1a;https://pan.quark.cn/s/b1c0c63d61ec 二、安装SQL Server 1.下载安装包后解压出来&#xff0c;双击打开 2.等待加载安装程序 3.点击基本安装 4.点击接受 5.点击浏览 6.在D盘新建文件夹 7.命名为【Sql Server】…

【GeekBand】C++设计模式笔记17_Mediator_中介者模式

1. “接口隔离” 模式 在组件构建过程中&#xff0c;某些接口之间直接的依赖常常会带来很多问题&#xff0c;甚至根本无法实现。采用添加一层间接&#xff08;稳定&#xff09;接口&#xff0c;来隔离本来互相紧密关联的接口是一种常见的解决方案。典型模式 FacadeProxyAdapte…