Posix线程/C++11多线程基础

0 阅读6分钟

Posix线程/C++11多线程

本文对比了POSIX pthreadsC++11 threads在多线程编程中的应用,包括线程创建、 线程组、线程传参及类成员函数作为线程函数的实现,适合初学者和开发者深入理解C++多线程 编程。

在C++11引进多线程之前,我们不得不用POSIX pthreads来编写多线程程序。

POSIX pthreads 是POSIX标准的一部分,它提供了一组函数和宏,用于创建和管理线程。
虽然 POSIX pthreads 功能强大,但它的API相对复杂,使用起来比较繁琐。

本文包括了以下内容:

  • POSIX多线程实践
  • C++11多线程实践
  • 类成员函数作为线程函数的实现

一、POSIX多线程实践

创建简单线程

创建一个线程,用pthread_join()阻塞,等待线程结束。

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

void *callback(void*)
{
    cout << "子线程ID: " << pthread_self() << endl;//获取当前线程ID
}

int main()
{
    pthread_t tid;
    pthread_create(&tid,NULL,callback,NULL);
    pthread_join(tid,NULL);
    
    return 0;
}

image-2.png

多个线程实现

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

static const int NUM_THREADS = 5; //线程数量

void *call_from_thread(void *arg) {
    cout << "Hello from thread: " << pthread_self() << endl;
    return NULL;
}

int main()
{
    pthread_t t[NUM_THREADS];
    for (int i = 0; i < NUM_THREADS; i++)
    {
        pthread_create(&t[i],NULL,call_from_thread,NULL);
    }
    
    for (int i = 0; i < NUM_THREADS; i++)
    {
        pthread_join(t[i],NULL);//等待线程结束
    }
 

    return 0;
}

image-3.png

当创建多个子线程时,外加主线程共六个线程,我们可以看到线程之间是平等竞争临界资源关系的,顺序是无序的,因此如果要明确确定顺序,则需要同步术语来改进,如互斥锁、条件变量等。

线程传参的实现

线程的传参可以是简单的数据类型,也可以是自定义数据类型,因此我们在编写线程传参的时候要留意。

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

static const int NUM_THREADS = 5;

struct thread_data {
  int thread_id;  
};
 
void *call_from_thread(void *arg) {
    struct thread_data *th_data = (struct thread_data *)arg;
    cout << "Hello from thread " << th_data->thread_id << endl;
    return NULL;
}

int main()
{
    pthread_t t[NUM_THREADS];
    thread_data td[NUM_THREADS];


    for(int i=0;i<NUM_THREADS;i++){
        td[i].thread_id = i;
        pthread_create(&t[i],NULL,call_from_thread,&td[i]); //td[i]作为参数传递给call_from_thread函数
    }
  
    cout << "Hello from main thread" << endl;

    for(int i=0;i<NUM_THREADS;i++){
        pthread_join(t[i],NULL);//等待线程结束 
    }
    
    return 0;
}

image-4.png

再一次验证了,线程之间是竞争、无序的。

二、C++11多线程实践

C++11增加了多线程,能够更简便的实现多线程编程,并且兼容POSIX pthreads。

简单线程

#include <iostream>
#include <thread>
#include <string>

using namespace std;

 //普通函数作为线程函数
void call_from_thread() {
      // 为了防止输出乱序,实际项目中通常加锁,这里为了演示简单省略
    cout << "Hello from thread" << endl;
}



int main()
{
   thread tid(call_from_thread); //创建、启动线程
   tid.join(); //等待线程结束
  
    cout << "Hello from main thread" << endl;

    //lambda表达式作为线程函数
    auto f = [](const std::string& name){
        cout << "Hello" << name << endl;
    };

    thread tid2(f,"Tom"); //创建、启动线程
    tid2.join(); //等待线程结束

    return 0;
}

image-5.png

C++11线程其实是和POSIX线程API创建的线程本质上是一样的,只是C++11线程封装了POSIX线程API,并且提供了更简洁的接口。

main函数创建了线程call_back函数,并在tid.join()处等待线程结束。如果忘记等待,main线程有可能先完成,导致程序提前退出,会杀死之前所创建的线程。

多个线程实现

#include <iostream>
#include <thread>

static const int NUM_THREADS = 10; // 线程数量

using namespace std;

// 普通函数作为线程函数
void call_from_thread()
{
    // 为了防止输出乱序,实际项目中通常加锁,这里为了演示简单省略
    cout << "Hello from thread" << endl;
}

int main()
{
    thread t[NUM_THREADS]; // 创建线程数组

    for (int i = 0; i < NUM_THREADS; ++i)
    {
        t[i] = thread(call_from_thread); // 创建、启动线程
    }

    cout << "Hello from main thread" << endl;

    for (int i = 0; i < NUM_THREADS; ++i)
    {
        t[i].join(); // 等待线程结束
    }

    return 0;
}

image-7.png 可以发现我们也可以在主线程之后做一些工作。

多个线程同时运送后没有特定的顺序,我们在编程时要确保线程不会修改临界资源,如果多个线程之间确实要竞争一些资源,我们需要编写更复杂的并行代码,使用类似nutex、atomic等方法避免上述问题。

线程传参的实现

#include <iostream>
#include <thread>
#include <string>

using namespace std;

// 普通函数作为线程函数
void call_from_thread(string& s)
{
    // 为了防止输出乱序,实际项目中通常加锁,这里为了演示简单省略
    cout << "Hello from thread :" << s << endl;
    s = "hello"; //修改传入的参数
}

int main()
{   
    string str = "Jaylan";
    thread t(&call_from_thread,std::ref(str)); // 创建线程,并传递参数,ref()表示显式的引用传递
    t.join(); // 等待线程结
    cout << "Hello from main thread=" << str << endl;

    return 0;
}

image-8.png 先阻塞了主线程,等待线程结束,然后输出修改后的参数。

三、类成员函数作为线程函数的实现

在线程中如何将一个类成员函数作为线程函数呢?

成员方法作为线程来执行,有以下好处:

  • 线程的变量传递会非常方便,直接读取成员变量即可。
  • 线程体作为对象的一部分,可以访问对象的私有变量和方法。

成员函数作为线程函数(POSIX threads)

语法:int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*routine)(void*), void* arg);

主要第三个参数routine是一个普通函数,而不是一个类成员函数,这是为什么? 先测试一下:

class AA
{
public:
    AA(const char* name);
    void sayHelo*();
    void stop();
private:
    const char* m_name;
    pthread_t m_thread; //线程
}

AA::AA(const char* name):m_name(name)
{
    pthread_create(&m_thread,NULL,AA::sayHelo,this);
}

image-9.png 分析报错信息,成员函数默认是非静态函数,那么我们尝试将pthread_create的第三个参数改为静态函数。

static void* createThread(void* arg)
{
    A* pa = (A*)arg;
    pa->sayHello();
    return (void*)0; 
}

AA::AA(const char* name):m_name(name)
{
    pthread_create(&m_thread,NULL,createThread,this);
}

我们通过static函数createThread作为第三个参数,同时使用第四个参数this线程传参,进而在createThread简间接调用class中的成员方法。 还有一种方法是:将routine函数声明为该类的友元函数,这样routine函数就可以直接调用该类的成员函数了。

还可以利用lambda函数,lambda函数可以捕获类成员变量,从而在lambda函数中调用类成员函数。

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

class AA
{
public:
    AA(const char* name);
    void sayHelo();
    void stop();
private:
    const char* m_name;
    pthread_t m_thread; //线程
};
AA::AA(const char* name):m_name(name)
{
    auto f = [](void* arg)
    {
        printf("word thread\n");
        AA* a = (AA*)arg;
        a->sayHelo();
        return (void*)0; //子线程退出,并返回一个空指针
    };
    pthread_create(&m_thread,NULL,f,this);
}

void AA::sayHelo()
{
    printf("hello,%s\n",m_name);
}

void AA::stop()
{
    pthread_join(m_thread,NULL); //主线程等待子线程结束
}

int main()
{
    pthread_t tid;
    AA a("张三");
    printf("main thread\n");
    a.stop();

    return 0;
}

image-10.png

主线程和子线程的输出顺序是随机的,因为线程之间是竞争、无序的。

成员函数作为线程函数(C++11 threads)

#include #include #include using namespace std;

class AA { public: AA(const string& name); ~AA(); void sayHello(); void stop(); private: string m_name; thread m_thread; //线程 };

AA::AA(const string& name):m_name(name), m_thread(&AA::sayHello,this){}

AA::~AA() { if(m_thread.joinable())//判断线程是否可join { m_thread.join(); //主线程等待子线程结束 } }

void AA::sayHello() { cout << "hello," << m_name << endl; }

void AA::stop() { if(m_thread.joinable())//判断线程是否可join { m_thread.join(); //主线程等待子线程结束 } }

int main() { AA a("张三"); cout << "main thread.\n"; a.stop();

return 0;

}


![image-11.png](https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/369b621084264dfba721bdcdeaa336dd~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5bCP546L5Zyo6L-b5q2l:q75.awebp?rk3s=f64ab15b&x-expires=1770971577&x-signature=xrP0cNhbbV2SJyjU6TJy%2B6V55f8%3D)

文章进行了简单的线程实践,欢迎大家一起交流学习。