欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C++多线程编程

程序员文章站 2024-01-20 10:55:04
概述 aspbian">OS:Raspbian 什么叫做多线程编程,大概意思就是说对多个任务同时进行控制,而且相互之间还需要协调。相信很多人和我一样在开始的时候,认为多线程编程...

概述

什么叫做多线程编程,大概意思就是说对多个任务同时进行控制,而且相互之间还需要协调。相信很多人和我一样在开始的时候,认为多线程编程是为了利用多核处理器,使程序运行的更快。但是,现在只要打开操作系统,那么肯定就不止一个任务在工作,所以,和多核没有什么关系。加快速度,从某种层面上来说的确有这个效果。但是,其作用的方式是不同的,有点懵?OK,我们一会再说。

在项目中,遇到了不少需要用到多线程编程的例子。有的是在C++库里需要用到,有的是在Java语言用到,包括开发安卓的过程中。像是在QT中,用到的库是QThread,在java中继承Thread类。在这里,我想用C++中的thread.h这个头文件里的函数来进行举例,这个头文件的方便移植且不需要增加其他的库。虽然,我目前实在Raspbian上面进行编程,但是可以保证在Linux平台下也可以正常运行,windows下mingw也是可以正常运行的,其他的自己试一下吧。

现在,先举几个在项目中用到多线程编程的例子吧。
1.armLinux的开发中,经常使用中断。(什么叫中断模式,百度百科:暂时停止当前程序的执行转而执行处理新情况的程序和执行过程。简单的来说:就是当运行到某处,暂停运行,直到从硬件处获得某个信号,比如说获得键盘某个键按下,以后有机会细讲)这时候,我们不可以让主进程暂停了,因为主进程还有其他操作。有时候,用到的中断程序不止一个。那么,就需要开多个线程每个中断都处于不同的线程当中,才能检测各个中断信号。
2.在开发安卓的过程中,有一些操作是不允许在主线程中进行操作。例如网络通信。其实也是可以理解的,网络通信大多都需要长时间操作,那么如果主线程已经在操作UI了,如果此时耗时太多在网络通信上面的话,UI的创建就会延迟,这样手机就会出现卡顿,我们的手机已经够卡的了,所以,安卓就不允许在主线程中进行网络通信。

多线程基本使用

创建两个线程,输出他们是第几个线程。

#include 
// 线程相关类,在类Unix下一般都有
#include 

using namespace std;
// 定义线程启动的函数
void* fun1(void* args)
{
    cout << "This is first Thread!\n";
}

void* fun2(void* args)
{
    cout << "This is second Thread!\n";
}

int main()
{
    // 线程结构体
    pthread_t pt1,pt2;
    // 创建线程,线程创建成功,返回值是0
    if(pthread_create(&pt1,NULL,fun1,NULL) != 0)
    {
        cout << "Error in create first Thread!\n";
    }
    if(pthread_create(&pt2,NULL,fun2,NULL) != 0)
    {
        cout << "Error in create second Thread!\n";
    }
    //等待线程结束后,结束进程
    pthread_exit(NULL);
    return 0;
}

注意:在这里编译的时候,需要在IDE中设置静态编译库。所以,我就直接用命令行来进行编译。

g++ -o thread0 thread0.cpp -lpthread
./thread0

大多数情况下会先输出第一个线程所要输出的内容,但是如果多次运行的话,会发现有时候线程二的内容会在线程一的内容出来前出现。OK,现在有三个问题了:
1.不是C++多线程编程吗,那么怎么在类里面使用呢?
2.怎么往线程里面传入参数呢?
3.如果我们必须要线程一完成以后再进行线程二,怎么办?

线程调用类中函数

如果线程调用类中的函数,那么必须将该函数生命为静态函数。
静态成员函数属于静态全局区,线程可以共享该区域。

#include 
// 线程相关类,在类Unix下一般都有
#include 

using namespace std;

// 定义一个类
class MyThread
{
private:
public:
    // 定义线程启动的函数,必须是静态的
    static void* fun1(void* args);
    static void* fun2(void* args);
};

void* MyThread::fun1(void* args)
{
    cout << "This is first Thread!\n";
}

void* MyThread::fun2(void* args)
{
    cout << "This is second Thread!\n";
}

int main()
{
    // 线程结构体
    pthread_t pt1,pt2;
    // 创建线程,线程创建成功,返回值是0
    if(pthread_create(&pt1,NULL,MyThread::fun1,NULL) != 0)
    {
        cout << "Error in create first Thread!\n";
    }
    if(pthread_create(&pt2,NULL,MyThread::fun2,NULL) != 0)
    {
        cout << "Error in create second Thread!\n";
    }
    //等待线程结束后,结束进程
    pthread_exit(NULL);
    return 0;
}

命令其实和上面一样的:

g++ -o thread1 thread1.cpp -lpthread
./thread1

传入参数

现在,往里面传入参数来确定分别是哪个线程来调用的。

#include 
// 线程相关类,在类Unix下一般都有
#include 
#include 

using namespace std;

// 定义一个类
class MyThread
{
public:
    // 定义线程启动的函数,必须是静态的
    static void* fun(void* args);
};

void* MyThread::fun(void* args)
{
    cout << "This is " << *((string*) args) << " Thread!\n";
}

int main()
{
    // 线程结构体
    pthread_t pt;
    string ss1,ss2;
    // 创建线程,线程创建成功,返回值是0
    ss1 = "first";
    if(pthread_create(&pt,NULL,MyThread::fun,(void*)&ss1) != 0)
    {
        cout << "Error in create first Thread!\n";
    }
    ss2 = "second";
    if(pthread_create(&pt,NULL,MyThread::fun,(void*)&ss2) != 0)
    {
        cout << "Error in create first Thread!\n";
    }
    //等待线程结束后,结束进程
    pthread_exit(NULL);
    return 0;
}

编译运行命令以后不写了,同上即可。
我们来看一下结果,结果有点奇怪。有时候是first在前,有时候second在前,这自然不用多说。但是还有一种情况是:段错误。所以,我们继续来控制一下每个线程运行的前后顺序。

线程控制

结束某个线程或者控制线程等待某个信号量

定义两个线程,两个线程启动,在第一次输入后,结束地一个线程,在第二次输入后,开始运行线程二中的实际内容。

#include 
#include 
#include 

using namespace std;

// 定义第一个线程类
class MyThread0
{
public:
    static bool run;
    // 定义线程启动的函数,必须是静态的
    static void* fun(void* args);
};

// 定义第二个线程类
class MyThread1
{
public:
    static bool waitRun;
    // 定义线程启动的函数,必须是静态的
    static void* fun(void* args);
};

bool MyThread0::run = true;
bool MyThread1::waitRun = true;

void* MyThread0::fun(void* args)
{
    int i = 0;
    while(run)
    {
        i = 1;
        // 执行的任务
    }
    cout << i << endl;
}

void* MyThread1::fun(void* args)
{
    while(waitRun);
    // 实际执行的任务
    cout << "MyThread1 is OK!" << endl;
}

int main()
{
    pthread_t pt;
    if(pthread_create(&pt,NULL,MyThread0::fun,NULL) != 0)
    {
        cout << "Error in create first Thread!\n";
    }
    if(pthread_create(&pt,NULL,MyThread1::fun,NULL) != 0)
    {
        cout << "Error in create first Thread!\n";
    }
    // 至此两个程序已经开始运行
    getchar();
    MyThread0::run = false;
    getchar();
    MyThread1::waitRun = false;
    pthread_exit(NULL);
    return 0;
}

我用这个方法很长时间了,杀人越货之利器,好理解,没有额外的函数,而且无论语言都可以直接移植。但是毕竟不是个特别好的办法,而且如果两个线程共享一个数据,那么就一定要先锁定某个线程,然后准备好资源后才可以启动另外一个线程,太过麻烦。而且一直要陷在死循环里暂停程序也不像个事。(其实,我是从嵌入式里面学到的)其实,在学习操作系统/数据库原理中对于并发性操作,都是相当需要注意的,每个线程的先后都要控制好,而且需要注意数据的保护和读写,这时候我们就需要引入互斥锁机制。

等待某个线程结束再开始其他线程

我们首先来看一下程序,程序中最后有个函数pthread_exit,这里的意思是等所有子线程结束后,主线程结束,也就是说到这个函数程序将会结束,以后所有的语句都不会再执行。而且,还有个问题就是等待所有线程结束,根本无法判断个别线程结束。那么,我们可不可以先等待某个线程结束,再继续运行下一步呢?

#include 
#include 
#include 

using namespace std;

void* change_value(void* args)
{
    cout << "This thread value is " << *((int *)args) <<  endl;
}

int main()
{
    int value1 = 1,value2 = 2;
    pthread_t pt1,pt2;
    int ret = pthread_create(&pt1, NULL, change_value, (void*)&value1);
    if(ret != 0)
    {
    cout << "pthread_create error:error_code=" << ret << endl;
    }
    //pthread_join的另外一个作用就是可以控制等待某个进程结束
    pthread_join(pt1,NULL);
    ret = pthread_create(&pt2, NULL, change_value, (void*)&value2);
    if(ret != 0)
    {
    cout << "pthread_create error:error_code=" << ret << endl;
    }
    pthread_join(pt2,NULL);
    return 0;
}

获得线程结束信息

我们首先来看一下程序,程序中最后有个函数pthread_exit,这里的意思是等子线程结束后,主线程结束,也就是说到这个函数程序将会结束,以后所有的语句都不会再执行。OK,那么我们即使获得线程结束信息,也无法表示。那么我们吧这条语句去了,去了的话会发现有的线程还没有运行完成,程序就已经结束了,怎么解决?当然也可以估计一下时间然后等待,或者设置一个公共变量,然后在主线程中利用循环暂停,但是,我们这里用系统提供给我们的函数来进行操作。

#include 
#include 
#include 

using namespace std;

void* change_value(void* args)
{
    cout << "This thread value is " << *((int *)args) <<  endl;
    int result = *((int *)args) + 10;
    pthread_exit((void*)result);
}

int main()
{
    int value1 = 1,value2 = 3;
    void* result;
    pthread_t pt1,pt2;
    int ret = pthread_create(&pt1, NULL, change_value, (void*)&value1);
    if(ret != 0)
    {
        cout << "pthread_create error:error_code=" << ret << endl;
    }
    pthread_join(pt1,&result);
    cout << "The thread1's result is " << (int)result << endl;
    ret = pthread_create(&pt2, NULL, change_value, (void*)&value2);
    if(ret != 0)
    {
        cout << "pthread_create error:error_code=" << ret << endl;
    }
    pthread_join(pt2,&result);
    cout << "The thread2's result is " << (int)result << endl;
    return 0;
}

互斥锁

互斥锁的作用是访问数据时可以保证是被一个线程所访问,并不能控制线程执行顺序的先后。

#include 
#include 

using namespace std;

// 定义第一个线程类
class MyThread
{
public:
    static int flag;
    // 定义线程启动的函数,必须是静态的
    static void* fun1(void* args);
    static void* fun2(void* args);
};

int MyThread::flag = 0;
pthread_mutex_t sum_mutex;
int temp;

void* MyThread::fun1(void* args)
{
    // 上锁
    pthread_mutex_lock(&sum_mutex);
    cout << "This is " << ++flag << " Thread1!\n";
    // 解锁
    pthread_mutex_unlock(&sum_mutex);
}

void* MyThread::fun2(void* args)
{
    pthread_mutex_lock(&sum_mutex);
    cout << "This is " << ++flag << " Thread2!\n";
    pthread_mutex_unlock(&sum_mutex);
}

int main()
{
    pthread_t pt;
    if(pthread_create(&pt,NULL,MyThread::fun1,NULL) != 0)
    {
        cout << "Error in create first Thread!\n";
    }
    if(pthread_create(&pt,NULL,MyThread::fun2,NULL) != 0)
    {
        cout << "Error in create first Thread!\n";
    }
    pthread_exit(NULL);
    cout << temp << endl;
    pthread_mutex_destroy(&sum_mutex);
    return 0;
}

基本到此为止,多线程的一些最基本的使用就到此结束了,在使用的过程中,发现自己的很多基础知识都有很多断层,大概这就是我用到哪里学到哪里的一个大BUG。这样也好,从最基本的开始一点一点来检查。