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

news2024/11/25 0:49:13

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

「归属专栏」Linux系统编程

「笔者」枫叶先生(fy)

「座右铭」前行路上修真我

「枫叶先生有点文青病」

「每篇一句」

纵有千古,横有八荒;

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

目录

三、 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...开始

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

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

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

现象一解释:

  • 主线程创建新线程太快了,新线程都没有机会运行,主线程就把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地址都不一样

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

获取线程ID 

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

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

 man 3 pthread_self 查看:

 

函数: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;
}

编译运行

 

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秒后整个进程退出

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

pthread_exit函数终止线程

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

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

  • 函数: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;
}

编译运行

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

pthread_cancel函数取消线程

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

man 3 pthread_cancel 查看:

函数: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

3.4 线程等待

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

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

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

man 3 pthread_join 查看:

函数: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;
}

编译运行

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

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

 

  • 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函数成功获取线程函数的返回值

解释如下图:

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

注意:调用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 查看:

 

函数: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;
}

编译运行

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上也能创建线程 

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

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

从内核上理解pthread库

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

 

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

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

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

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

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

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;
}

运行结果

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

--------------------- END ----------------------

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

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

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

相关文章

「OceanBase 4.1 体验」|连续多年支撑双11的国产原生分布式数据库

目录 前言 正文 1. OceanBase 4.1发布 2.部署oceanbase 安装环境 2.1下载 all-in-one 安装包 2.2解压安装包 2.3执行安装脚本 2.4设置环境变量 2.5查看文件位置 2.6快速启动demo环境 2.7访问数据库 3.oceanbase的优点 4.oceanbase与 Oracle 兼容性 5.oceanbase与…

王学岗码牛jetpack系列之ViewModel

ViewModel的目的&#xff1a;存储数据&#xff0c;以注重生命周期的方式管理界面的相关数据 viewModel的特性&#xff1a;1,数据持久化&#xff0c;不依赖于Activity的生命周期,有自己独立的生命周期 2,异步回调不会造成内存泄漏 3&#xff0c;隔离Model层与View层 4&#xff0…

EXCEL美化画图,请不要嫌弃excel画图,那是简单上手的不二之选

一、美化折线图 这是测试数据 这是一般人用excel画的折线图 这是二般人用excel画的折线图 具体实施步骤 1.调整成黑色或者灰度 选择灰色图即可 2.修改数据标识符号&#xff0c;并取消网格 注意每根线条都要设置这个数据标记符号 取消网格线很简单&#xff0c;点击网格…

Qt第一天:创建Qt项目

方式一&#xff1a;使用向导创建 打开Qt Creator 界面选择 New Project或者选择菜单栏 【文件】-【新建文件或项目】菜单项 弹出New Project对话框&#xff0c;选择Qt Widgets Application 选择【Choose】按钮&#xff0c;弹出如下对话框 设置项目名称和路径&#xff0c;按照…

【Mysql】基础篇:DQL(data query language):查询数据库中表的记录——select

博主简介&#xff1a;努力学习的大一在校计算机专业学生&#xff0c;热爱学习和创作。目前在学习和分享&#xff1a;数据结构、Go&#xff0c;Java等相关知识。博主主页&#xff1a; 是瑶瑶子啦所属专栏: Mysql从入门到精通近期目标&#xff1a;写好专栏的每一篇文章 目录 一、…

【C++11】多线程+IO流

目录 一、C11线程库 1、每个线程都有独立的栈空间 2、加锁的位置 3、CSA操作 4、C的类模板atomic&#xff08;原子操作&#xff09; 5、lock_guard&#xff08;RAII风格的锁&#xff09;/unique_lock&#xff08;可随时释放锁&#xff09; 6、条件变量&#xff08;用于互…

微信小程序学习实录1(wxml文档、引入weui、双向数据绑定)

微信小程序学习实录 一、wxml文档二、新建页面快捷方式三、微信小程序引入weui四、双向数据绑定1.wxml渲染层2.js逻辑层 五、微信小程序跳转到H5 一、wxml文档 <!-- index.wxml --> <view><!-- 数据绑定 --><view><text>{{name}}</text>&…

机器学习笔记 基于OpenCV的文本检测和识别模块

一、EAST文本检测器简述 east是一种高效准确的场景文本检测器,网络模型如下。该模型是一个适用于文本检测的全卷积神经网络,输出单词或文本行的密集每像素预测。这就省去了诸如候选建议、文本区域形成和单词分割等中间步骤。后处理步骤仅包括对预测的几何形状进行阈值处理和N…

【分布式】一致性哈希和哈希槽

当我们拥有了多台存储服务器之后&#xff0c;现在有多个key&#xff0c;希望可以将这些个key均匀的缓存到这些服务器上&#xff0c;可以使用哪些方案呢&#xff1f; 1. 普通哈希取模法 1.1 直接哈希取模 这是一种最容易想到的方法&#xff0c;使用取模算法hash&#xff08;k…

前端三剑客React框架第一课入门的学习

前端三大框架React框架第一课入门的学习 前端三大框架的介绍 React:由facebook贡献&#xff0c;是一个基于javascript的前端库。它主要关注ui组件的构建&#xff0c;通过virtual dom等技术手段实现高效的渲染优化&#xff0c;可以与各种其他库和框架搭配使用&#xff0c;也有…

作为一个C++新手,我感兴趣的C++开源项目

2023年4月30日&#xff0c;周日晚上。 昨天完成了一个C项目后&#xff0c;想再开始一个C项目&#xff0c;但不知道做什么&#xff0c;于是决定看看有什么好的C开源项目。 今晚在网上逛了一圈后&#xff0c;发现了好多有趣的C开源项目。 参考文章&#xff1a; GitHub Top 10 …

【MySQL】十二,索引的概念

为什么使用索引 提高数据查询的效率。 定义 MySQL官方对索引的定义为&#xff1a;索引&#xff08;Index&#xff09;是帮助MySQL高效获取的数据的数据结构。 使用索引的优点 提高数据检索的效率&#xff0c;降低数据库的IO成本。通过创建唯一索引&#xff0c;可以保证数据…

科研闭环指南|学术论文撰写经验总结

前言&#xff1a;最近完成了自己人生中第一篇学术论文长文的撰写&#xff0c;从2023年4月12日完成初稿到2023年4月30日完成终稿这半个多月的时间里&#xff0c;在多位老师与师兄的帮助下&#xff0c;前前后后改了六七个版本&#xff0c;才改到大致满意的最终版&#xff08;在此…

设计模式——模板模式

导航&#xff1a; 【黑马Java笔记踩坑汇总】JavaSEJavaWebSSMSpringBoot瑞吉外卖SpringCloud黑马旅游谷粒商城学成在线设计模式牛客面试题 目录 模板模式 1、基本介绍 2、模板模式解决豆浆制作问题 3、钩子方法 4、Spring 框架AbstractApplicationContext抽象类 模板模式 …

C语言从入门到精通第16天(指针的定义与基本使用)

指针的定义与基本使用 什么是指针&#xff1f;指针变量的定义指针变量的基本使用 什么是指针&#xff1f; 在使用指针之前我们需要对指针进行初步的了解&#xff0c;首先我们要知道什么是指针&#xff1f; 通过前面的学习我们已经知道了内存的存储方式&#xff0c;他是通过一…

【LeetCode股票买卖系列:121. 买卖股票的最佳时机 | 一次遍历 | 暴力递归=>记忆化搜索=>动态规划】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

在群晖NAS上快速搭建属于自己的Git Server

群晖NAS套件中心是有Git Server套件的&#xff0c;只要在套件中心安装即可。但是需要注意的是&#xff1a; git 相关的命令需要使用 SSH 客户端连接到NAS上进操作。配置git时需要使用 管理员权限账户&#xff0c;而 push / fetch 使用的账户一般都为普通用户&#xff0c;一定要…

ChatGPT提示词工程(一):Guidelines

目录 一、说明二、安装环境三、Guidelines准则一&#xff1a;写出明确而具体的说明方法1&#xff1a;使用分隔符清楚地表示输入的不同部分方法2&#xff1a;用结构化输出&#xff1a;如直接要求它以HTML或者JSON格式输出方法3&#xff1a;请模型检查是否满足条件方法4&#xff…

01-环境搭建 尚筹网

环境搭建 一、项目结构搭建 ​ 模块关系 ​ parent模块仅仅用来确定各个Maven依赖的版本 ​ webui、component、entity模块继承自parent模块 ​ util、reverse模块属于独立工程&#xff0c;不参与继承与聚合 ​ 且webui依赖于component&#xff0c;component依赖于entity…

Cont. TF-IDF (BigData Data Mining)

Cont. 举例 例1 词频 (TF) 是一词语出现的次数除以该文件的总词语数。 假如一篇文件的总词语数是100个&#xff0c;而词语“母牛”出现了3次&#xff0c;那么“母牛”一词在该文件中的词频就是3/1000.03。 一个计算文件频率 (IDF) 的方法是文件集里包含的文件总数除以测定有多…