『Linux』第九讲:Linux多线程详解(二)_ 线程控制

这篇具有很好参考价值的文章主要介绍了『Linux』第九讲:Linux多线程详解(二)_ 线程控制。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

「前言」文章是关于Linux多线程方面的知识,上一篇是 Linux多线程详解(一),今天这篇是 Linux多线程详解(二),讲解会比较细,下面开始!

「归属专栏」Linux系统编程

「主页链接」个人主页

「笔者」枫叶先生(fy)

「枫叶先生有点文青病」「每篇一句」

纵有千古,横有八荒;

前途似海,来日方长。
——梁启超

『Linux』第九讲:Linux多线程详解(二)_ 线程控制

目录

三、 Linux线程控制

3.1 POSIX线程库

3.2 线程创建

3.3 线程终止

3.4 线程等待

3.5 线程分离

3.6 重新认识pthread库

3.7 封装线程


三、 Linux线程控制

3.1 POSIX线程库

前面我们使用的 pthread线程库是归属 POSIX线程库,pthread线程库是 POSIX线程库的一部分,POSIX线程库也叫原生线程库,遵守 POSIX标准:

  • 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
  • 要使用这些函数库,要通过引入头文<pthread.h>
  • 链接这些线程函数库时要使用编译器命令的 “-lpthread” 选项

POSIX线程库错误检查:

  • 传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。
  • pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做),而是将错误代码通过返回值返回
  • pthreads同样也提供了线程内的 errno 变量,以支持其它使用 errno 的代码。对于 pthreads函数的错误,建议通过返回值业判定,因为读取返回值要比读取线程内的errno变量的开销更小

3.2 线程创建

这个在上一篇多线程(一)已经详细介绍了一部分,这里就不赘述了

线程创建使用的函数是 pthread_create

函数:pthread_create
 
作用: pthread_create - create a new thread(创建一个新线程)
 
 
头文件:#include <pthread.h>
 
函数原型
 int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);
 
参数
    第一个参数thread,代表线程ID,是一个输出型参数,pthread_t是一个无符号整数
    第二次参数attr,用于设置创建线程的属性,传入空表示使用默认属性
    第三个参数start_routine,是一个函数的地址,该参数表示新线程启动后要跳转执行的代码
    第四个参数arg,是start_routine函数的参数,用于传入
 
返回值
成功返回0,失败返回错误码

下面进行代码测试,让主线程创建一批线程,注意循环创建线程时没有进行sleep

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;

void* start_routine(void* args)
{
    string name = static_cast<const char*>(args);//static_cast 安全的进行强制类型转换,C++11
    while(1)
    {
        cout << "new thread create success, 线程编号:" << name << endl;
        sleep(1);
    }
}

int main()
{
#define NUM 10
    //创建一批线程
    for(int i = 0; i < NUM; i++)
    {
        pthread_t tid;
        char namebuffer[64];
        snprintf(namebuffer, sizeof(namebuffer), "%s:%d", "thread", i+1);//i+1 使线程下标从1开始
        // pthread_create(&tid, nullptr, start_routine, (void*)"thread one");
        pthread_create(&tid, nullptr, start_routine, namebuffer);//给线程带上编号
        //没有进行 sleep
        //sellp(1);
    }
    //主线程
    while(1)
    {
        cout << "new thread create success, I am main thread" << endl;
        sleep(1);
    }

    return 0;
}

编译运行,观察现象,现象一:线程的编号都是一样的,并不是我们预想的从 1、2、3...开始

『Linux』第九讲:Linux多线程详解(二)_ 线程控制

保持进程运行,ps -aL 查看,10个线程确实创建出来了,加上主线程一共 11个线程

『Linux』第九讲:Linux多线程详解(二)_ 线程控制

把 sleep 注释的代码放开,再次编译运行

『Linux』第九讲:Linux多线程详解(二)_ 线程控制

运行结果,现象二:编号 1-10 都有了

『Linux』第九讲:Linux多线程详解(二)_ 线程控制

现象一解释:

  • 主线程创建新线程太快了,新线程都没有机会运行,主线程就把10个新线程创建完毕了,
  • 而传参namebuffer传过去的是 缓冲区namebuffer的起始地址,
  • 第十个线程创建完成之后,缓冲区的内容都被第十个线程的编号内容覆盖了,所以第一次现象线程的编号都是 10

注意:创建的新线程谁先运行??答案是不确定,完全由调度器决定

上面的 start_routine函数,被多个执行流执行,该函数处于可重入状态。start_routine函数也是可重入函数,因为没有产生二义性

测试代码,对每个线程的的cnt进行取地址,观察地址是否相同

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;

//把参数封成结构体
class ThreadData
{
public:
    pthread_t tid;
    char namebuffer[64];
};

void* start_routine(void* args)
{
   ThreadData* td = static_cast<ThreadData*>(args);//static_cast 安全的进行强制类型转换,C++11
   int cnt = 10;
    while(cnt)
    {
        cout << "cnt:" << cnt-- << "  &cnt:" << &cnt << endl;
        sleep(1);
    }
    delete td;
    return nullptr;
}

int main()
{
#define NUM 10
    //创建一批线程
    for(int i = 0; i < NUM; i++)
    {
        ThreadData* td = new ThreadData();//每次循环new的都是一个新对象
        snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", i+1);//i+1 使线程下标从1开始
        pthread_create(&td->tid, nullptr, start_routine, td);
    }
    //主线程
    while(1)
    {
        cout << "new thread create success, name:main thread" << endl;
        sleep(1);
    }

    return 0;
}

编译运行,观察到每个线程的cnt地址都不一样

在函数内部定义的变量叫局部变量,具有临时性,在多线程的情况下依旧适用,因为每个线程都有自己的独立栈结构

『Linux』第九讲:Linux多线程详解(二)_ 线程控制

获取线程ID 

常见获取线程ID的方式有两种:

  • 创建线程时通过输出型参数获得
  • 通过调用pthread_self函数获得

 man 3 pthread_self 查看:

『Linux』第九讲:Linux多线程详解(二)_ 线程控制

函数:pthread_self

头文件:#include <pthread.h>

函数原型: pthread_t pthread_self(void);

 测试代码,创建一个新线程,新线程获取自己的线程ID

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

void* start_routine(void* args)
{
    string name = static_cast<const char*>(args);
    while(1)
    {
        cout << name << " running..., ID:" << pthread_self() << endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, start_routine, (void*)"thread 1:");
    while(1)
    {
        cout << "main thread" << endl;
        sleep(1);
    }
    return 0;
}

编译运行

『Linux』第九讲:Linux多线程详解(二)_ 线程控制

3.3 线程终止

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

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

return终止线程

在线程中使用return代表当前线程退出,但是在main函数中使用return代表整个进程退出,也就是说只要主线程退出了那么整个进程就退出了,此时该进程曾经申请的资源就会被释放,而其他线程会因为没有了资源,自然而然的也退出了

测试代码,主线程创建3个新线程后,休眠2秒,然后进行return,那么整个进程也就退出了 

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;

class ThreadData
{
public:
    pthread_t tid;
    char namebuffer[64];
};

void* start_routine(void* args)
{
   ThreadData* td = static_cast<ThreadData*>(args);//static_cast 安全的进行强制类型转换,C++11
   int cnt = 10;
    while(cnt)
    {
        cout << "new thread create success, name:" << td->namebuffer << "  cnt:" << cnt-- << endl;
        sleep(1);
    }
    delete td;
    return nullptr;
}

int main()
{
#define NUM 3
    //创建一批线程
    for(int i = 0; i < NUM; i++)
    {
        ThreadData* td = new ThreadData();
        snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", i+1);//i+1 使线程下标从1开始
        pthread_create(&td->tid, nullptr, start_routine, td);
    }
    //主线程
    cout << "new thread create success, name:main thread" << endl;
    sleep(2);//主线程两秒后退出
    return 0;
}

 编译运行,2秒后整个进程退出

『Linux』第九讲:Linux多线程详解(二)_ 线程控制

如果其他线程执行到return,代表该线程结束,线程退出

『Linux』第九讲:Linux多线程详解(二)_ 线程控制

pthread_exit函数终止线程

注意:exit 是用来终止进程的,任何一个执行流调用 exit,都会使整个进程退出

pthread_exit函数的功能就是终止线程,man 3 pthread_exit 查看:

『Linux』第九讲:Linux多线程详解(二)_ 线程控制

  • 函数:pthread_exit
  • 头文件:#include <pthread.h>
  • 函数原型: void pthread_exit(void *retval);
  • 参数retval,线程退出时的退出码信息,如果不关心,可以设置为nullptr
  • 无返回值,跟进程一样,线程结束的时候无法返回它的调用者(自身)

例如,在下面代码中,创建了3个线程,我们使用pthread_exit函数终止线程

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;

class ThreadData
{
public:
    pthread_t tid;
    char namebuffer[64];
};

void* start_routine(void* args)
{
   ThreadData* td = static_cast<ThreadData*>(args);//static_cast 安全的进行强制类型转换,C++11
   int cnt = 10;
    while(cnt)
    {
        cout << "new thread create success, name:" << td->namebuffer << "  cnt:" << cnt-- << endl;
        sleep(1);
        pthread_exit(nullptr);//每个线程执行到这里就会退出
    }
    delete td;
    return nullptr;
}

int main()
{
#define NUM 3
    //创建一批线程
    for(int i = 0; i < NUM; i++)
    {
        ThreadData* td = new ThreadData();
        snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", i+1);
        pthread_create(&td->tid, nullptr, start_routine, td);
    }
    //主线程
    while(1)
    {
        cout << "new thread create success, name: main thread" << endl;
        sleep(1);
    }
    return 0;
}

编译运行

『Linux』第九讲:Linux多线程详解(二)_ 线程控制

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

pthread_cancel函数取消线程

线程是可以被取消的,我们可以使用pthread_cancel函数取消某一个线程

man 3 pthread_cancel 查看:

『Linux』第九讲:Linux多线程详解(二)_ 线程控制

函数:pthread_cancel

头文件:#include <pthread.h>

函数原型:
        int pthread_cancel(pthread_t thread);

参数:
    thread:被取消线程的ID

返回值:线程取消成功返回0,失败返回错误码

 测试代码,让线程执行5秒后再取消线程

#include <iostream>
#include <vector>
#include <cassert>
#include <unistd.h>
#include <pthread.h>
using namespace std;

class ThreadData
{
public:
    int number; //线程编号
    pthread_t tid;//线程ID
    char namebuffer[64];//缓冲区
};

void* start_routine(void* args)
{
    ThreadData* td = static_cast<ThreadData*>(args);
   int cnt = 10;
    while(cnt)
    {
        cout << td->namebuffer << "  cnt:" << cnt-- << endl;
        sleep(1);
    }
    pthread_exit((void*)td->number);
}

int main()
{
    vector<ThreadData*> threads;
#define NUM 5
    //创建一批线程
    for(int i = 0; i < NUM; i++)
    {
        ThreadData* td = new ThreadData();
        td->number = i+1;//线程编号
        snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", td->number);//把需要传递的参数信息格式化到缓冲区namebuffer中
        pthread_create(&td->tid, nullptr, start_routine, td);
        threads.push_back(td);//把每个线程的信息push到threads里面
    }
//主线程
   for(auto& iter : threads)
   {
        cout << "create thread: " << iter->namebuffer << " : " << iter->tid << " success" << endl;
   }
   sleep(5);
   //线程取消
   for(auto& iter : threads)
   {
        pthread_cancel(iter->tid);
        cout << "pthread_cancel: " << iter->namebuffer << endl;
   }
   //线程等待
   for(auto& iter : threads)
   {
        void* ret = nullptr;
        int n = pthread_join(iter->tid, &ret);
        assert(n == 0);
        cout << "join: " << iter->namebuffer <<  " success, thread_exit_code: " << (long long)ret << endl;
        delete iter;
   }
   cout << "main thread quit" << endl;
    return 0;
}

编译运行,

注意:一个线程被取消,它的退出码是 -1

『Linux』第九讲:Linux多线程详解(二)_ 线程控制

3.4 线程等待

一个线程被创建出来,这个线程就如同进程一般,也是需要被等待的。如果主线程不对新线程进行等待,那么这个新线程的资源也是不会被回收的。所以线程需要被等待,如果不等待会产生类似于“僵尸进程”的问题,也就是内存泄漏,关于线程产生类似于“僵尸进程”的问题,我们无法查看,线程并没有类似于僵尸进程的概念

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

进行线程等待的函数是 pthread_join,功能:进行线程等待

man 3 pthread_join 查看:

『Linux』第九讲:Linux多线程详解(二)_ 线程控制

函数:pthread_join

头文件: #include <pthread.h>

函数原型:int pthread_join(pthread_t thread, void **retval); 

参数:
    thread:被等待线程的ID
    retval:线程退出时的退出码信息,不关心设置为nullptr

返回值:
线程等待成功返回0,失败返回错误码

例如,在下面的测试代码中我们先不关心线程的退出信息,进行线程等待

#include <iostream>
#include <vector>
#include <cassert>
#include <unistd.h>
#include <pthread.h>
using namespace std;

class ThreadData
{
public:
    pthread_t tid;
    char namebuffer[64];
};

void* start_routine(void* args)
{
   ThreadData* td = static_cast<ThreadData*>(args);
   int cnt = 5;
    while(cnt)
    {
        cout << td->namebuffer << "  cnt:" << cnt-- << endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    vector<ThreadData*> threads;
#define NUM 5
    //创建一批线程
    for(int i = 0; i < NUM; i++)
    {
        ThreadData* td = new ThreadData();
        snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", i+1);//i+1 使线程下标从1开始
        pthread_create(&td->tid, nullptr, start_routine, td);
        threads.push_back(td);//把每个线程的信息push到threads里面
    }
    //主线程
   for(auto& iter : threads)
   {
        cout << "create thread: " << iter->namebuffer << " : " << iter->tid << " success" << endl;
   }
   //线程等待
   for(auto& iter : threads)
   {
        int n = pthread_join(iter->tid, nullptr);
        assert(n == 0);
        cout << "join: " << iter->namebuffer << "success" << endl;
        delete iter;
   }
   cout << "main thread quit" << endl;
    return 0;
}

编译运行

『Linux』第九讲:Linux多线程详解(二)_ 线程控制

下面谈线程退出码的问题,即返回值的问题

  • void* retval 和 void** retval 有什么关系??
  • 线程函数start_routine函数的返回值类型也是 void*, start_routine函数的返回值返回到哪里??
  • 我们怎么获取线程的退出码,即线程的返回值??

『Linux』第九讲:Linux多线程详解(二)_ 线程控制

  • pthread_join函数的参数 void** retval 是一个输出型参数,用来获取线程函数结束时,返回的退出结果
  • void** retval 是用来获取线程函数返回的退出结果,因为线程函数的返回值是 void*,所以需要用 void** 来接受 void*
  • 注意:线程函数返回的退出结果是返回在线程库当中,参数 void** retval 需要去线程库里面接受才可以返回

tips:指针是一个地址(字面值),是一个右值,指针变量是一个变量(变量里面保存着指针的地址),是一个左值,现在使用的Linux一般是64位的,所以指针是占8字节的

测试代码,线程函数返回 66666,pthread_join函数的第二个参数去线程库里面接受再返回

#include <iostream>
#include <vector>
#include <cassert>
#include <unistd.h>
#include <pthread.h>
using namespace std;

class ThreadData
{
public:
    int number; //线程编号
    pthread_t tid;//线程ID
    char namebuffer[64];//缓冲区
};

void* start_routine(void* args)
{
    ThreadData* td = static_cast<ThreadData*>(args);
   int cnt = 5;
    while(cnt)
    {
        cout << td->namebuffer << "  cnt:" << cnt-- << endl;
        sleep(1);
    }
    //return (void*)td->number;//返回线程的编号,返回在线程库中,函数的返回类型是void*,需要进行强转void*
    return (void*)66666;//方便观察
}

int main()
{
    vector<ThreadData*> threads;
#define NUM 5
    //创建一批线程
    for(int i = 0; i < NUM; i++)
    {
        ThreadData* td = new ThreadData();
        td->number = i+1;//线程编号
        snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", td->number);//把需要传递的参数信息格式化到缓冲区namebuffer中
        pthread_create(&td->tid, nullptr, start_routine, td);
        threads.push_back(td);//把每个线程的信息push到threads里面
    }
//主线程
   for(auto& iter : threads)
   {
        cout << "create thread: " << iter->namebuffer << " : " << iter->tid << " success" << endl;
   }
   //线程等待
   for(auto& iter : threads)
   {
        void* ret = nullptr;//用于接收线程函数的返回值
        int n = pthread_join(iter->tid, &ret);//对ret取地址 == void**,需要去线程库中接收,再返回
        assert(n == 0);
        //原来ret是void*,需要强转int,恢复整型; 
        //由于我所处的平台是64位的,指针是8字节,不能用int进行强转,会报错,因为int是4字节,
        //需要用 long long 进行强转,long long 是8字节
        cout << "join: " << iter->namebuffer <<  " success, threadnumber: " << (long long)ret << endl;
        delete iter;
   }
   cout << "main thread quit" << endl;
    return 0;
}

编译运行,pthread_join函数成功获取线程函数的返回值

『Linux』第九讲:Linux多线程详解(二)_ 线程控制

解释如下图:

『Linux』第九讲:Linux多线程详解(二)_ 线程控制

pthread_exit函数:void pthread_exit(void *retval),这个也是返回线程的退出信息,测试结果如下:

『Linux』第九讲:Linux多线程详解(二)_ 线程控制

注意:调用pthread_join函数的线程将挂起等待,直到线程终止等待成功。

int pthread_join(pthread_t thread, void **retval);

thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:

  • 如果thread线程通过return返回,retval 所指向的单元里存放的是thread线程函数的返回值。
  • 如果thread线程被别的线程调用 pthread_ cancel 异常终掉,retval 所指向的单元里存放的是常数 PTHREAD_ CANCELED(-1)。
  • 如果thread线程是自己调用 pthread_exit 终止的,retval 所指向的单元存放的是传给 pthread_exit 的参数。
  • 如果对thread线程的终止状态不感兴趣,可以传空给 retval 参数

3.5 线程分离

  • 默认情况下,新创建的线程是joinable(可以被等待)的,线程退出后,需要对其进行 pthread_join 操作,否则无法释放资源,从而造成系统泄漏
  • 如果不关心线程的返回值,join是一种负担,这个时候我们可以将该线程进行分离,当线程退出时,自动释放线程资源
  • 分离线程的函数叫做pthread_detach

man 3 pthread_detach 查看:

『Linux』第九讲:Linux多线程详解(二)_ 线程控制

函数:pthread_detach
detach:分开,脱离

头文件:#include <pthread.h>

函数原型:
        int pthread_detach(pthread_t thread);

参数:
    thread:被分离线程的ID

返回值:
    线程分离成功返回0,失败返回错误码

 测试代码,创建了一个新线程,然后对新线程进行分离,那么此后主线程就不需要在对新线程进行join了

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

string changeID(const pthread_t& thread_id)
{
    char tid[128];
    snprintf(tid, sizeof(tid), "0x%x", thread_id);
    return tid;
}

void* start_routine(void* args)
{
    string threadname = static_cast<const char*>(args);
    int cnt = 5;
    while(cnt--)
    {
        cout << threadname << " running..., threadID:" << changeID(pthread_self()) << endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, start_routine, (void*)"thread 1");
    pthread_detach(tid);//分离线程

    //线程默认是 joinable的,线程分离了之后不允许进行等待
    //pthread_join(tid, nullptr);
    string mainID = changeID(pthread_self());//主线程ID
    while(1)
    {
        cout << "main running..., mainID:" << mainID << ", new threadID:" << changeID(tid) << endl;
        sleep(1);
    }
    return 0;
}

编译运行

『Linux』第九讲:Linux多线程详解(二)_ 线程控制

3.6 重新认识pthread库

从语言上理解pthread库

C++11也有自己的线程库,测试代码如下,使用C++11的线程库创建一个新线程

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

void thread_run()
{
    while (true)
    {
        std::cout << "我是新线程..." << std::endl;
        sleep(1);
    }
}

int main()
{
    //创建新线程
    std::thread t1(thread_run);
    //主线程
    while (true)
    {
        std::cout << "我是主线程..." << std::endl;
        sleep(1);
    }
    //线程等待
    t1.join();

    return 0;
}

 编译运行,在Linux上也能创建线程 

『Linux』第九讲:Linux多线程详解(二)_ 线程控制

如何看待C++11中的线程库??

任何语言,在linux中如果要实现多线程,必定要是用pthread库。C++11的多线程,在Linux环境中,本质是对pthread库的封装 

从内核上理解pthread库

  • 用户创建的线程在 pthread库中,pthread库又会帮我们调用系统调用接口clone,帮我们创建轻量级线程
  • Linux用户级线程 : 内核轻量级线程 = 1 : 1
  • 这两个的关系是一对一的,用户创建一个线程,在内核中就会创建一个轻量级进程
  • 用户关心的线程属性在 pthread中,而内核提供的是线程执行流的调度

『Linux』第九讲:Linux多线程详解(二)_ 线程控制

如何理解我们前面创建线程时的线程ID??如何理解每个线程的独立栈结构?? 

  • pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址

进程运行时动态库被加载到内存,然后通过页表映射到进程地址空间中的共享区,此时该进程内的所有线程都是能看到这个动态库的

『Linux』第九讲:Linux多线程详解(二)_ 线程控制

每一个新线程在共享区都有这样一块区域对新线程的描述,因此我们要找到一个用户级线程只需要找到该线程内存块的起始地址,然后就可以获取到该线程的各种信息

  • 所以,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址,指向这个线程结构体的开始地址
  • 每个线程都有一个独立的栈结构,这个栈就在描述线程的结构体里面,即在pthread库中
  • 每个线程创建之后,都是使用自己独立的栈
  • 主线程所用的栈,也就是我平时所说的栈,地址空间中的栈

『Linux』第九讲:Linux多线程详解(二)_ 线程控制

3.7 封装线程

目的:对Linux线程接口进行封装,使封装后的接口可以像C++11线程库里面的一样使用

Thread.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cassert>
#include <functional>
#include  <pthread.h>

// 异常 == if: 意料之外用异常或者if判断
// assert: 意料之中用assert

//Context需要使用Thread,需要声明一下
class Thread;

//上下文,当成一个大号的结构体
class Context
{
public:
    Thread *this_;
    void *args_;
public:
    Context():this_(nullptr), args_(nullptr)
    {}
    ~Context(){}
};

class Thread
{
public:
    typedef std::function<void*(void*)> func_t;//线程函数
    const int num = 1024;
public:
    //构造初始化并创建线程
    Thread(func_t func, void *args = nullptr, int number = 0): func_(func), args_(args)
    {
        char buffer[num];
        snprintf(buffer, sizeof buffer, "thread-%d", number);
        name_ = buffer;
        
        Context *ctx = new Context();
        ctx->this_ = this;
        ctx->args_ = args_;
        int n = pthread_create(&tid_, nullptr, start_routine, ctx);
        //编译debug的方式发布的时候存在,release方式发布,assert就不存在了,n就是一个定义了
        assert(n == 0); 
        //但是没有被使用的变量,在有些编译器下会有warning
        (void)n;
    }

    void *run(void *args){ return func_(args); }

    // 类内成员,有缺省参数this指针,无法直接调用对应的函数,func_(args_)error
    // 在类内创建线程,想让线程执行对应的方法,需要将方法设置成为static
    // 静态方法不能调用成员方法或者成员变量
    // 所以需要借助一个结构体Context,完成调用函数的工作
    static void *start_routine(void *args) 
    {
        Context *ctx = static_cast<Context *>(args);
        void *ret = ctx->this_->run(ctx->args_);
        delete ctx;
        return ret;
    }

    // 线程等待函数
    void join()
    {
        int n = pthread_join(tid_, nullptr);
        assert(n == 0);
        (void)n;
    }
    ~Thread() {}
private:
    std::string name_;//线程名称
    func_t func_;//线程要执行的函数
    void* args_;//给线程函数传递的参数
    pthread_t tid_;//线程ID
};

测试代码

#include <iostream>
#include <unistd.h>
#include "Thread.hpp"
using namespace std;

//新线程
void* start_routine(void* args)
{
    string name = static_cast<const char*>(args);
    while(1)
    {
        cout << "new thread create success, name: " << name << endl;
        sleep(1);
    }
}

int main()
{
    //不想传(void*)"thread 1", 1 参数,还需要进行封装
    Thread t1(start_routine, (void*)"thread 1", 1);
    while(1)
    {
        cout << "main thread" << endl;
        sleep(1);
    }
    return 0;
}

运行结果

『Linux』第九讲:Linux多线程详解(二)_ 线程控制

线程创建完结,下一篇进入互斥量和生产消费者模型

--------------------- END ----------------------文章来源地址https://www.toymoban.com/news/detail-430233.html

「 作者 」 枫叶先生
「 更新 」 2023.4.30
「 声明 」 余之才疏学浅,故所撰文疏漏难免,
          或有谬误或不准确之处,敬请读者批评指正。

到了这里,关于『Linux』第九讲:Linux多线程详解(二)_ 线程控制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 第九讲 单片机驱动彩色液晶屏 控制RA8889软件:显存操作

    目录 第一讲 单片机最小系统STM32F103C6T6通过RA8889驱动彩色液晶屏播放视频 第二讲 单片机最小系统STM32F103C6T6控制RA8889驱动彩色液晶屏硬件框架 第三讲 单片机驱动彩色液晶屏 控制RA8889软件:如何初始化 第四讲 单片机驱动彩色液晶屏 控制RA8889软件:绘图 第五讲 单片机驱动彩色液

    2024年01月16日
    浏览(50)
  • MySQL第九讲·索引怎么提高查询的速度?

    你好,我是安然无虞。 在超市信息系统刚刚开始运营的时候,因为数据量很少,每一次的查询都能很快拿到结果。但是,系统运转时间长了以后,数据量不断地累积,变得越来越庞大,很多查询的速度就变得特别慢。这个时候,我们就采用了 MySQL 提供的高效访问数据的方法

    2024年02月04日
    浏览(48)
  • Git第九讲 Git stash暂存消息

    git stash 是一个在 Git 中用于临时保存未提交的更改的命令。它可以帮助你在切换分支或处理紧急任务时,将当前工作目录中的修改保存起来,以便稍后恢复。 git stash 的使用方法如下: 当你想要暂存当前的修改时,运行以下命令: 可选地,你可以提供一个简短的描述性消息,

    2024年02月08日
    浏览(52)
  • Redis 7 第九讲 微服务集成Redis 应用篇

            Jedis是redis的java版本的客户端实现,使用Jedis提供的Java API对Redis进行操作,是Redis官方推崇的方式;并且,使用Jedis提供的对Redis的支持也最为灵活、全面;不足之处,就是编码复杂度较高。   直连案列    获取Redis连接对象 Redis 案列 关闭Jedis对象   池案列  获取

    2024年02月09日
    浏览(34)
  • 设计模式第九讲:常见重构技巧 - 去除不必要的!=

    项目中会存在大量判空代码,多么丑陋繁冗!如何避免这种情况?我们是否滥用了判空呢?本文是设计模式第九讲,讲解常见重构技巧:去除不必要的!= 通常是这样的 初步的,使用Apache Commons,Guvava,Hutool等 StringUtils 考虑用Assert断言 逐级判断空,还是抛出自定义异常,还是

    2024年02月11日
    浏览(42)
  • 前端技术学习第九讲:VUE基础语法---VUE常用指令

    在VUE学习中,通常使用相关指令使VUE对象中的内容与网页进行挂载绑定,是我们的数据与视图之间产生关联,完成渐进式动态效果。VUE指令都会以“v-”开头。 指令名 描述 v-text 将文本内容挂载到页面元素中 v-html 将html代码展示到页面元素中 v-bind 将内容解析成为js,绑定至页

    2024年02月13日
    浏览(43)
  • 【Linux】详解线程控制 -- 线程用法 | 线程等待 | 线程ID及地址空间布局

    (关于 用户 → 库 → OS :具体可看下面线程地址空间布局) 这个 clone 我们不用,这是OS提供给第三方库所用的接口 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“ pthread_ ”开头的,要使用这些函数库,要通过引入头文 pthread.h ,链接这些线程函数库时要

    2024年02月15日
    浏览(55)
  • 考研数二第九讲 函数凹凸性证明,求极值以及拐点及渐近线

    函数的凹凸性即对一个在某区间A上连续的函数,它的图像上凸或者上凹,则分别称为凸函数或者凹函数。而对于在某个区间内既有凹图像又有凸图像,则将凹图像所在区间称为函数的凹区间,凸图像所在区间则称为凸区间。 中点定义法 同样是观察凹凸函数的图像,发现凹函

    2024年02月09日
    浏览(46)
  • 【Java基础教程】(十五)面向对象篇 · 第九讲:抽象类和接口——定义、限制与应用的细节,初窥模板设计模式、工厂设计模式与代理设计模式~

    掌握 抽象类和接口的定义、使用、区别、常见设计模式; 抽象类是代码开发中的重要组成部分,利用抽象类可以明确地定义子类需要覆写的方法,这样相当于在语法程度上对子类进行了严格的定义限制,代码的开发也就更加标准。下面具体介绍抽象类的概念。 普通类可以直

    2024年02月16日
    浏览(48)
  • 【Linux】Linux线程概念和线程控制

    线程是进程内的一个执行流。 我们知道,一个进程会有对应的PCB,虚拟地址空间,页表以及映射的物理内存。所以我们把这一个整体看做一个进程,即进程=内核数据结构+进程对应的代码和数据。我们可以这样看待虚存:虚拟内存决定了进程能够看到的\\\"资源\\\"。因为每一个进

    2024年02月04日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包