C++ 多线程编程

多线程是多任务处理的一种特殊形式,多任务处理允许让电脑同时运行两个或两个以上的程序

有两种类型的多任务处理 基于进程基于线程

  • 基于进程的多任务处理是程序的并发执行

    Windows 下 QQ 可以多开就是多进程处理的一个例子

  • 基于线程的多任务处理是同一程序的片段的并发执行

    迅雷下载就是一个多线程任务的例子

多线程 程序包含可以同时运行的两个或多个部分,这样的程序中的每个部分称为一个线程,每个线程定义了一个单独的执行路径

本章节是基于 POSIX 操作系统的,因为我们要用它的 <pthread.h>libpthread.a 来开发多线程程序

POSIX 表示可移植操作系统接口 ( Portable Operating System Interface of UNIX,缩写为 POSIX )

目前主流的 LinuxMac OS 都属于 POSIX 系统,但是,抱歉,Windows 不是

所以,如果你使用的是 Windows 电脑,别灰心,先练练其它的,以后有的是机会学习这个

pthread

  1. 基于 POSIX 开发多线程程序需要加载 <pthread.h>

    #include <pthread.h>
    
  2. pthread 提供了一个类型 pthread_t 用来表示一个线程

  3. pthread 库不是 LinuxMac OS 系统默认的库,编译连接时需要加上静态库 libpthread.a,就像下面这样

    g++ main.cpp -lpthread
    

创建线程 pthread_create()

pthread.h 提供了 pthread_create() 用来创建一个 POSIX 线程,并立即让它执行

pthread_create (thread, attr, start_routine, arg)

创建线程成功时,函数返回 0,若返回值不为 0 则说明创建线程失败

参数 描述
thread 指向线程标识符指针
attr 一个不透明的属性对象,用来设置线程属性
可以指定线程属性对象,也可以传递 NULL
start_routine 线程运行函数起始地址,一旦线程被创建就会执行
arg 运行函数的参数
它必须通过把引用作为指针强制转换为 void 类型进行传递
如果没有传递参数,可以使用 NULL

我们写一个范例来演示下 pthread_create()

/**
 * file: main.cpp
 * author: 简单教程(www.twle.cn)
 *
 * Copyright © 2015-2065 www.twle.cn. All rights reserved.
 */

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

// 线程要运行的函数

void* greeting(void* args)
{
    std::cout << "Hello www.twle.cn" << std::endl;

    return NULL;
}


int main()
{
    // 定义线程的 id 变量
    pthread_t tid;

    // 创建线程
    // 参数依次是:创建的线程id,线程参数,调用的函数,传入的函数参数
    int ret = pthread_create(&tid, NULL, greeting, NULL);

    // 创建失败
    if (ret != 0)
    {
        std::cout << "pthread_create error: error_code = ";
        std::cout << ret << std::endl;
    }

    return 0;
}

编译运行上面的 C++ 代码,输出结果如下

$ g++ main.cpp -lpthread && a.out
main.cpp:18:1: warning: control reaches end of non-void function [-Wreturn-type]
}
^
1 warning generated.

发生了什么事?怎么什么都没有输出?

别着急啊,原因是这样的,就是呢,main() 函数虽然创建了一个线程,但是它没有等它运行完可能自己就先执行完了,所以就没有任何输出了

要等待新创建的线程运行完,main() 再退出,就要用到 pthread 提供的另一个函数 pthread_exit()

终止线程 pthread_exit()

pthread_exit() 函数用来终止一个 POSIX 线程,其实它的作用更像是向创建它的人说我要退出啦...

pthread_exit (status)

pthread_exit() 用于显式地退出一个线程,通常情况下,pthread_exit() 函数是在线程完成工作后无需继续存在时被调用

线程执行完任务了也会自己退出,不用显示调用 pthread_exit() ,不然有点像脱裤子放屁,多此一举

一般情况下,pthread_create() 创建的线程会在 main() 函数执行完毕时自动终止

对,这就是上面范例没有任何输出的原因

但如果在 main() 函数中显示的调用了 pthread_exit(),那么主线程(可以理解为 main() 函数) 就会继续等待它创建的线程全部退出,自己再退出

我们把上面那个范例完善下,让 main() 调用 pthread_exit() 等待子线程退出

/**
 * file: main.cpp
 * author: 简单教程(www.twle.cn)
 *
 * Copyright © 2015-2065 www.twle.cn. All rights reserved.
 */

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

// 线程要运行的函数

void* greeting(void* args)
{
    std::cout << "Hello www.twle.cn" << std::endl;
    return NULL;
}


int main()
{
    // 定义线程的 id 变量
    pthread_t tid;

    // 创建线程
    // 参数依次是:创建的线程id,线程参数,调用的函数,传入的函数参数
    int ret = pthread_create(&tid, NULL, greeting, NULL);

    // 创建失败
    if (ret != 0)
    {
        std::cout << "pthread_create error: error_code = ";
        std::cout << ret << std::endl;
    }

    pthread_exit( NULL );

    return 0;
}

编译运行上面的 C++ 代码,输出结果如下

$ g++ main.cpp -lpthread && a.out
Hello www.twle.cn

唠叨几句,我一开始很不能理解为什么加一个 pthread_exit(NULL) 就会等待子线程退出,后来我想了想,其实这个的意思就是告诉 main() 的调用者,我要退出了,可以吗? 调用者一看,你还有子线程没运行完呢,你再等等

创建一打线程

我们已经学会了如何创建单个线程,如果要创建多个呢?比如创建一打 (12个),要怎么做呢?

别紧张,都一样,区别只是要创建一个数组来装这一打线程的 id 值

/**
 * file: main.cpp
 * author: 简单教程(www.twle.cn)
 *
 * Copyright © 2015-2065 www.twle.cn. All rights reserved.
 */

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

// 线程要运行的函数

void* greeting(void* args)
{
    std::cout << "Hello www.twle.cn\n";
    return NULL;
}

// 定义线程数量
#define NUM_THREADS 12


int main()
{
    // 定义线程的 id 变量,多个变量使用数组
    pthread_t tids[NUM_THREADS];

    for(int i = 0; i < NUM_THREADS; ++i)
    {
        int ret = pthread_create(&tids[i], NULL, greeting, NULL);
        if (ret != 0)
        {
           std::cout << "pthread_create error: error_code=";
           std::cout << ret << std::endl;
        }
    }


    pthread_exit(NULL);

    return 0;
}

编译运行上面的 C++ 代码,输出结果如下

$ g++ main.cpp -lpthread && a.out
Hello www.twle.cn
Hello www.twle.cn
Hello www.twle.cn
Hello www.twle.cn
Hello www.twle.cn
Hello www.twle.cn
Hello www.twle.cn
Hello www.twle.cn
Hello www.twle.cn
Hello www.twle.cn
Hello www.twle.cn
Hello www.twle.cn

pthread_create() 中的 arg 参数

上面的范例一口气创建了 12 个线程输出了 12 个 Hello www.twle.cn,但是,鬼知道哪个是哪个输出的啊,有没有办法标记下呢?

有的,就是 pthread_create() 中最后那个 arg 参数

arg 参数是主动传递给运行函数 (start_routine) 的参数,它必须强制转换为 void 类型进行传递

有了这个参数,我们就可以把当前线程的顺序传递给 greeting() 函数了

/**
 * file: main.cpp
 * author: 简单教程(www.twle.cn)
 *
 * Copyright © 2015-2065 www.twle.cn. All rights reserved.
 */

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

// 线程要运行的函数

void* greeting(void* args)
{
    // 对传入的参数进行强制类型转换
    // 由无类型指针变为整形数指针,然后再读取
    int tid = *((int*)args);

    printf("Thread %2d  say: Hello www.twle.cn\n",tid);
    return NULL;
}

// 定义线程数量
#define NUM_THREADS 12


int main()
{
    // 定义线程的 id 变量,多个变量使用数组
    pthread_t tids[NUM_THREADS];

    // 创建一个 index 数组用于保存线程顺序
    int index[NUM_THREADS];

    for(int i = 0; i < NUM_THREADS; ++i)
    {
        index[i] = i+1;

        int ret = pthread_create(&tids[i], NULL, greeting, (void *)&(index[i]));
        if (ret != 0)
        {
            printf("pthread_create error: error_code=%d\n",ret);
        }
    }


    pthread_exit(NULL);

    return 0;
}

为什么抛弃了 <iostream><stdio> ,那是因为好理解,不然你可以试一试,试完了就会问我一大堆的问题了

编译运行上面的 C++ 代码,输出结果如下

$ g++ main.cpp -lpthread && a.out
Thread  1  say: Hello www.twle.cn
Thread  3  say: Hello www.twle.cn
Thread  2  say: Hello www.twle.cn
Thread  4  say: Hello www.twle.cn
Thread  5  say: Hello www.twle.cn
Thread  6  say: Hello www.twle.cn
Thread  7  say: Hello www.twle.cn
Thread  8  say: Hello www.twle.cn
Thread  9  say: Hello www.twle.cn
Thread 10  say: Hello www.twle.cn
Thread 11  say: Hello www.twle.cn
Thread 12  say: Hello www.twle.cn

向线程传递参数

终于看清了哪个是哪个输出的了,但是前篇一律的 Hello www.twle.cn 也是烦人啊,这个能否也作为参数传递呢?

答案是肯定的,我们可以构造一个结构体,然后通过传递结构体的方法达到传递多个参数

/**
 * file: main.cpp
 * author: 简单教程(www.twle.cn)
 *
 * Copyright © 2015-2065 www.twle.cn. All rights reserved.
 */

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

// 定义一个结构体作为参数
struct thread_data{
   int  tid;
   const char *msg;
};


// 线程要运行的函数

void* greeting(void* args)
{
    struct thread_data *data;

    data = (struct thread_data *) args;


    printf("Thread %2d  say: Hello %s\n",data->tid,data->msg);
    return NULL;
}

// 定义线程数量
#define NUM_THREADS 5


// 定义 5 个问候对象
const char *sayto[5] = {
    "Python",
    "PHP",
    "Perl",
    "www.twle.cn",
    "C++"
};


int main()
{
    // 定义线程的 id 变量,多个变量使用数组
    pthread_t tids[NUM_THREADS];

    struct thread_data td[NUM_THREADS];

    for(int i = 0; i < NUM_THREADS; ++i)
    {
        td[i].tid = i+1;
        td[i].msg = sayto[i];

        int ret = pthread_create(&tids[i], NULL, greeting, (void *)&(td[i]));
        if (ret != 0)
        {
            printf("pthread_create error: error_code=%d\n",ret);
        }
    }


    pthread_exit(NULL);

    return 0;
}

编译运行上面的 C++ 代码,输出结果如下

$ g++ main.cpp -lpthread && a.out
Thread  2  say: Hello PHP
Thread  3  say: Hello Perl
Thread  1  say: Hello Python
Thread  4  say: Hello www.twle.cn
Thread  5  say: Hello C++

参考文档

  1. IEEE and The Open Group <pthread.h>

C++ 基础教程

关于   |   FAQ   |   我们的愿景   |   广告投放   |  博客

  简单教程,简单编程 - IT 入门首选站

Copyright © 2013-2022 简单教程 twle.cn All Rights Reserved.