您好,欢迎访问非今科技!登录后台查看权限
  • 欢迎大神光临
  • 有朋自远方来 不亦悦乎

pthread线程库在C/C++中的基础用法

码叔博客 dz2015 2017-08-27 1930 次浏览 0个评论

这篇文章通过示例代码探索pthread线程库在C/C++中的基础用法,理论比较少,这是老刘的学习笔记,尽管老刘还没有实战经验,但是写的例子都是力求先提出需求,然后用现有的编程知识尝试去实现需求,之后就是出现bug或者不足,最之后不得不引入新的知识点去解决bug或者完善程序。老刘认为,学一个技能而不知道这个技能用来干什么,等于没学嘛!

多线程.PNG

线程的创建

概要

pthread线程库使用方法pthread_create函数创建并开始运行线程。参考手册http://man7.org/linux/man-pages/man3/pthread_create.3.html。

#include <pthread.h>
/**
 * 创建线程。
 * @param thread 线程id
 * @param attr 属性,一般可设置为NULL
 * @param start_routine 执行在子线程的函数
 * @param arg 传递到子线程函数的参数
 * @return 如果成功返回0,失败返回错误码
 */
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
          void *(*start_routine) (void *), void *arg);

例1.1

任务:创建一个线程。

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
 
/**
 * This method runs in a child thread.
 * @param args
 * @return
 */
void *threadA_run(void *arg) {
    printf("thread A start...\n");
    _sleep(1000); // TODO something
    printf("thread A stop.\n");
    return NULL;
}
 
int main() {
    // This is a unsigned integer.You may understand as id or handle.
    pthread_t threadA; 
    pthread_create(&threadA, NULL, threadA_run, NULL);
    printf("thread main stop.\n");
    return 0;
}
运行结果:
thread main stop.

从上面运行结果来看线程A完全没有机会执行,这是因为默认子线程在主线程退出后自动被系统销毁。

例1.2

任务:修改例1.1,使得子线程有机会执行完成。

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
 
/**
 * This method runs in a child thread.
 * @param args
 * @return
 */
void *threadA_run(void *arg) {
    printf("thread A start...\n");
    _sleep(1000); // TODO something
    printf("thread A stop.\n");
    return NULL;
}
 
int main() {
    pthread_t threadA; 
    pthread_create(&threadA, NULL, threadA_run, NULL);
    _sleep(1100);
    printf("thread main stop.\n");
    return 0;
}
运行结果
thread A start...
thread A stop.
thread main stop.

上面程序因为主线程睡眠了足够的时间才退出,所以线程A有机会执行完毕。在实际应用中子线程执行时间可不会是固定的,这时主线程如何等待子线程退出?用pthread_join函数可以达到目的。

线程join

概要

pthread_join函数,join with a terminated thread(将thread的执行插入调用线程执行直到其终止, 时间轴上的join,使得两个线程变为串行执行),换一种理解就是调用线程等待另 一个线程thread退出,如果thread还在执行,函数会阻塞,直到thread结束,如果已经结束,那么就会直接返回。参考手册http://man7.org/linux/man-pages/man3/pthread_join.3.html。

#include <pthread.h>
/**
 * 让调用这个方法的线程等待thread退出。如果retval不是NULL,thread的退出状态将存放到retval中。
 * @param thread 线程id
 * @param retval 获取线程函数返回值,如果不需要可以填NULL
 * @return 如果成功返回0,失败返回错误码
 */
int pthread_join(pthread_t thread, void **retval);

例2.1

任务:修改例1.2,实现主线程准确地在子线程退出后再退出。

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
 
/**
 * This method runs in a child thread.
 * @param args
 * @return
 */
void *threadA_run(void *arg) {
    printf("thread A start...\n");
    _sleep(1000); // TODO something
    printf("thread A stop.\n");
    return NULL;
}
 
int main() {
    pthread_t threadA; // This is a unsigned integer.You may understand as id or handle.
    char *retval;
    char arg[128];
    pthread_create(&threadA, NULL, threadA_run, NULL);
    pthread_join(threadA, NULL);
    printf("thread main stop.\n");
    return 0;
}
运行结果
thread A start...
thread A stop.
thread main stop.

例2.2

任务:写一个例子体现多线程间的协作。

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdint.h>
 
/**
 * To calc data.
 * @param calc input
 * @return calc output
 */
void *calc(void *arg) {
    uint32_t *input = (uint32_t *) arg;
    uint32_t *output = (uint32_t *) malloc(sizeof(uint32_t));
    *output = 0;
    while ((*input)--) {
        (*output)++;
    }
    return output;
}
 
 
int main() {
    pthread_t threadA, threadB;
    uint32_t *retvalA, *retvalB;
    uint64_t result;
    uint32_t argA = UINT32_MAX / 10, argB = UINT32_MAX / 10;
    pthread_create(&threadA, NULL, calc, &argA);
    pthread_create(&threadB, NULL, calc, &argB);
    pthread_join(threadA, (void **) &retvalA);
    pthread_join(threadB, (void **) &retvalB);
    result = (uint64_t) (*retvalA) + (uint64_t) *retvalB;
    free(retvalA);
    free(retvalB);
    printf("result=%llu.\n", result);
    return 0;
}
运行结果
result=858993458.


上面这个例子演示了三个线程配合完成一个综合计算。retvalAretvalB的计算过程是一个比较耗时的过程,但是没有互相依赖,因此可以并行计算;而result的计算必须等待retvalAretvalB都计算出来才能进行计算,于是我们用pthread_join分别等待AB线程结束。这个例子还涉及如何传递一个参数到子线程函数中以及如何获取子线程函数的返回值(子程序执行状态)。

互斥锁

正式介绍互斥锁之前,我们先看一个例子。

例3.1

任务:两个线程不断往同一个缓存写不同的单词,主线程不断打印单词,要求打印出的单词始终是完整的。

#include <stdio.h>
#include <pthread.h>
#include <mem.h>
 
char wordBuffer[128] = {'\0'};
 
void *threadA_run(void *arg) {
    char word[] = {'H', 'i', '!', '\n', '\0'};
    while (1) {
        memcpy(wordBuffer, word, sizeof(word));
    }
}
 
void *threadB_run(void *arg) {
    char word[] = {'h', 'e', 'l', 'l', 'o', '\n', '\0'};
    while (1) {
        memcpy(wordBuffer, word, sizeof(word));
    }
}
 
 
int main() {
    // This is a unsigned integer.You may understand as id or handle.
    pthread_t threadA, threadB;
    pthread_create(&threadA, NULL, threadA_run, NULL);
    pthread_create(&threadB, NULL, threadB_run, NULL);
    char tmp[sizeof(wordBuffer)];
    while (1) {
        memcpy(tmp, wordBuffer, sizeof(wordBuffer));
        printf(tmp);
    }
}
运行结果
Hi!
Hi!
he!lhi!lHil
hil
hel
o
hi!lo
He!
Hel
Hi!lo
hil
hellhello
he!lo
hi!
o
hil
HillHil


上面输出显然不符合要求,这是因为三个线程可能同时访问同一片内存,导致结果混乱,这种混乱可以认为是随机不可预测的。要实现输出完整单词的要求,实际上只要保证这三个线程操作wordBuffer在时间轴上不重叠即可,下面该互斥锁登场了。

概要

互斥锁可以保证lock成功到unlock之间的代码不会同时被执行。pthread_mutex_t可以在定义的时候直接用PTHREAD_MUTEX_INITIALIZER初始化,这叫静态初始化,不需手动释放;也可以用pthread_mutex_init方法初始化,这叫动态初始化,动态初始化应该在不使用该锁的时候调用pthread_mutex_destroy释放资源。

#include <pthread.h>
/**
 * 初始化一个锁。
 * @param __mutex 锁
 * @param __mutexattr 属性
 * @return 如果成功返回0,失败返回错误码
 */
int pthread_mutex_init (pthread_mutex_t *__mutex,
                               const pthread_mutexattr_t *__mutexattr);
/**
 * 销毁一个锁。
 * @param __mutex 锁
 * @return 如果成功返回0,失败返回错误码
 */
int pthread_mutex_destroy (pthread_mutex_t *__mutex);
/**
 * 上锁。
 * @param mutex 锁
 * @return 如果成功返回0,失败返回错误码
 */
int pthread_mutex_lock(pthread_mutex_t *mutex);
/**
 * 尝试上锁。
 * @param mutex 锁
 * @return 如果成功返回0,失败返回错误码
 */
int pthread_mutex_trylock(pthread_mutex_t *mutex);
/**
 * 解锁。
 * @param __mutex 锁
 * @return 如果成功返回0,失败返回错误码
 */
int pthread_mutex_unlock (pthread_mutex_t *__mutex);

例 3.2

任务:利用互斥锁完实现例3.1一样的效果。

#include <stdio.h>
#include <pthread.h>
#include <mem.h>
 
char wordBuffer[128] = {'\0'};
pthread_mutex_t  mutexBuf = PTHREAD_MUTEX_INITIALIZER;
 
void *threadA_run(void *arg) {
    char word[] = {'H', 'i', '!', '\n', '\0'};
    while (1) {
        pthread_mutex_lock(&mutexBuf);
        memcpy(wordBuffer, word, sizeof(word));
        pthread_mutex_unlock(&mutexBuf);
    }
}
 
void *threadB_run(void *arg) {
    char word[] = {'h', 'e', 'l', 'l', 'o', '\n', '\0'};
    while (1) {
        pthread_mutex_lock(&mutexBuf);
        memcpy(wordBuffer, word, sizeof(word));
        pthread_mutex_unlock(&mutexBuf);
    }
}
 
 
int main() {
    // This is a unsigned integer.You may understand as id or handle.
    pthread_t threadA, threadB;
    pthread_create(&threadA, NULL, threadA_run, NULL);
    pthread_create(&threadB, NULL, threadB_run, NULL);
    char tmp[sizeof(wordBuffer)];
    while (1) {
        pthread_mutex_lock(&mutexBuf);
        memcpy(tmp, wordBuffer, sizeof(wordBuffer));
        pthread_mutex_unlock(&mutexBuf);
        printf(tmp);
    }
}
运行结果
hello
Hi!
hello
Hi!
Hi!
Hi!
hello
Hi!
Hi!
hello
hello
Hi!
Hi!


条件变量

现在场景模拟小游戏很火呀,比如某宝弱智的养小鸡,O(∩_∩)O~!我们也可以来玩玩场景模拟,下面就玩一个有关“妈妈叫你回家吃饭了”的任务。

例4.1

任务:模拟一个母亲和儿子的一天日常(简陋版本,O(∩_∩)O~),实现母亲(一个线程)叫儿子(另一个线程)起床、吃饭、睡觉的通知行为,儿子打印出母亲的通知消息,当母亲叫儿子睡觉的时候退出线程,母亲在儿子退出线程后退出程序。

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

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

char msg[1024] = {0};

void *son_run(void *argv) {
    while (1) {
        // 后记[1]:可以在这个位置也就是上锁前添加消息查询,如果没有消息
        // 就不用上锁,继续循环查询,这样能尽可能减少锁定时间
        pthread_mutex_lock(&mutex);
        if(msg[0] == '\0') {
            pthread_mutex_unlock(&mutex);
            // 后记[2]:在这里睡眠会导致锁定时间变长,不好,可见这个例子当时写的时候,考虑不周全
            sleep(1);
            continue;
        }
        printf("msg:%s\n", msg);
        if(0 == strcmp(msg, "It's time for bed")) {
            pthread_mutex_unlock(&mutex);
            break;
        }
        msg[0] = '\0';
        pthread_mutex_unlock(&mutex);
    }
    printf("Son thread exit.\n");
    return NULL;
}

int main() {
    pthread_t  thread_son;
    pthread_create(&thread_son, NULL, son_run, NULL);
    for (int i = 0;; ++i) {
        sleep(2);
        if(0 == i) {
            pthread_mutex_lock(&mutex);
            strcpy(msg, "It's time to get up");
            pthread_mutex_unlock(&mutex);
        } else if(1 == i) {
            pthread_mutex_lock(&mutex);
            strcpy(msg, "It's time for breakfast");
            pthread_mutex_unlock(&mutex);
        } else if(2 == i) {
            pthread_mutex_lock(&mutex);
            strcpy(msg, "It's time for lunch");
            pthread_mutex_unlock(&mutex);
        } else if(3 == i) {
            pthread_mutex_lock(&mutex);
            strcpy(msg, "It's time for dinner");
            pthread_mutex_unlock(&mutex);
        } else if(4 == i) {
            pthread_mutex_lock(&mutex);
            strcpy(msg, "It's time for bed");
            pthread_mutex_unlock(&mutex);
            break;
        }
    }
    pthread_join(thread_son, NULL);
    printf("Mother thread exit.\n");
    return 0;
}

运行效果:

msg:It's time to get up
msg:It's time for breakfast
msg:It's time for lunch
msg:It's time for dinner
msg:It's time for bed
Son thread exit.
Mother thread exit.

上面儿子为了知道母亲的通知,不得不一直查询msg缓存区,看看是否有消息,msg缓存区是两个线程公用的,所以访问必须动用线程锁,儿子线程在没有消息的时候,适当睡眠,这可以有效减少查询和操作锁的次数,但是是以降低实时性为代价的。如此勤快的儿子,也是不能忍了,有没有更加优雅的方式,塑造一个慵懒的儿子形象?

概述

condition variable,条件变量,适合于一个线程或者多个线程比如1、2、3...线程空闲的时候处于阻塞状态,一个或者多个线程如a、b、c、d...线程为1、2、3...线程准备某种条件/资源,a、b、c、d准备完成的时候通知1、2、3...其中一个或者全部线程唤醒,当然除了多对多,一对多,多对1,一对一的场景也是适合的。一些注意的地方:1、一个pthread_cond_t应当和一个pthread_mutex_t配合使用,pthread_cond_t cond使用PTHREAD_COND_INITIALIZER静态初始化或者使用pthread_cond_init函数动态初始化,如果使用动态初始化,应该在不用这个条件变量的时候使用pthread_cond_destroy释放资源;2、进入等待状态前应该先上锁,pthread_cond_wait/pthread_cond_timedwait内部会自动解锁,但在返回前,函数内部一定会自动重新上锁。

#include <pthread.h>

/**
 * 使用__cond_attr初始化条件变量,__cond_attr设置为NULL,将使用默认属性初始化条件变量。
 * @param __cond 要初始化的条件变量
 * @param __cond_attr 属性
 * @return 如果成功返回0,失败返回错误码
 */
int pthread_cond_init (pthread_cond_t *__cond, 
                       const pthread_condattr_t *__cond_attr);

/**
 * 催毁条件变量。
 * @param __cond 要催毁的条件变量
 * @return 如果成功返回0,失败返回错误码
 */
int pthread_cond_destroy (pthread_cond_t *__cond);

/**
 * 唤醒一个用条件变量__cond等待的线程。
 * @param __cond 目标条件变量
 * @return 如果成功返回0,失败返回错误码
 */
int pthread_cond_signal (pthread_cond_t *__cond);

/* Wake up all threads waiting for condition variables COND.  */
/**
 * 唤醒所有用条件变量__cond等待的线程。
 * @param __cond 目标条件变量
 * @return 如果成功返回0,失败返回错误码
 */
int pthread_cond_broadcast (pthread_cond_t *__cond);

/**
 * 等待条件变量__cond的一个信号或者广播。调用该函数之前你应该确保已经用
 * __mutex上锁。
 * @param __cond 目标条件变量
 * @param __mutex 配合__cond的锁
 * @return 如果成功返回0,失败返回错误码
 */
int pthread_cond_wait (pthread_cond_t * __cond,
                       pthread_mutex_t * __mutex);
/**
 * 在指定时间内等待条件变量__cond的一个信号或者广播,调用该函数之前你应该确保已经用
 * __mutex上锁。
 * @param __cond 目标条件变量
 * @param __mutex 配合__cond的锁
 * @param __abstime 绝对时间
 * @return
 */
int pthread_cond_timedwait (pthread_cond_t * __cond, 
                            pthread_mutex_t * __mutex,
                            const struct timespec * __abstime);


例4.2

任务:使用互斥锁和条件量实现上个例4.1。

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

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

char msg[1024];

void *son_run(void *argv) {
    while (1) {
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond, &mutex);
        printf("msg:%s\n", msg);
        if(0 == strcmp(msg, "It's time for bed")) {
            pthread_mutex_unlock(&mutex);
            break;
        }
        pthread_mutex_unlock(&mutex);
    }
    printf("Son thread exit.\n");
    return NULL;
}

int main() {
    pthread_t  thread_son;
    pthread_create(&thread_son, NULL, son_run, NULL);
    for (int i = 0;; ++i) {
        sleep(2);
        if(0 == i) {
            pthread_mutex_lock(&mutex);
            strcpy(msg, "It's time to get up");
            pthread_cond_broadcast(&cond);
            pthread_mutex_unlock(&mutex);
        } else if(1 == i) {
            pthread_mutex_lock(&mutex);
            strcpy(msg, "It's time for breakfast");
            pthread_cond_broadcast(&cond);
            pthread_mutex_unlock(&mutex);
        } else if(2 == i) {
            pthread_mutex_lock(&mutex);
            strcpy(msg, "It's time for lunch");
            pthread_cond_broadcast(&cond);
            pthread_mutex_unlock(&mutex);
        }  else if(3 == i) {
            pthread_mutex_lock(&mutex);
            strcpy(msg, "It's time for dinner");
            pthread_cond_broadcast(&cond);
            pthread_mutex_unlock(&mutex);
        } else if(4 == i) {
            pthread_mutex_lock(&mutex);
            strcpy(msg, "It's time for bed");
            pthread_cond_broadcast(&cond);
            pthread_mutex_unlock(&mutex);
            break;
        }
    }
    pthread_join(thread_son, NULL);
    printf("Mother thread exit.\n");
    return 0;
}

让C++实例方法运行在pthread子线程中

pthread注册的线程方法不能是实例方法,下面是一个解决这个问题的简单实现:Thread类,实际注册:子线程方法是静态的_run,然后将实例作为_run的参数传入,在_run中调用实例方法run。使用Thread只需要继承并实现run方法,启动线程调用start方法。

Thread.h

#ifndef CPPTEST_THREAD_H
#define CPPTEST_THREAD_H
 
#include <pthread.h>
 
class Thread {
private:
    pthread_t id;
public:
    virtual void run() =0;
 
    static void *_run(void *_this);
 
    void start();
};
 
#endif //CPPTEST_THREAD_H

Thread.c

#include "Thread.h"
 
void Thread::start() {
    pthread_create(&id, nullptr, _run, this);
}
 
void *Thread::_run(void *_this) {
    ((Thread *) _this)->run();
    return nullptr;
}


已有 1930 位网友参与,快来吐槽:

发表评论