NDK4 C++线程操作

线程简单使用

pthread_create:创建线程;参数1:线程Id,注意这里是需要一个指针;参数2:线程属性指针;参数3:函数指针----->异步任务;参数4:函数指针的参数传递 指针;
pthread_t:线程Id;
参数3的异步任务,是个指针函数哦,我这里写的customPThreadMethod方法;
第四个参数,注意是传递给异步任务 的行参;


#include <iostream>
#include <string>
// TODO 第四步:导入 线程的 头文件
#include <pthread.h>
using namespace std;

//void *(PTW32_CDECL *start) (void *)
void * customPThreadMethod(void * pVoid){   //能够接收所有的类型
    // static_cast<int *>(pVoid) 先将空指针转换成 int类型的指针;
    // * 再取出转换后的int类型的指针的值
    int result = *static_cast<int *>(pVoid);
    printf("我是线程任务。。执行了,result:%d\n",result);
    for (int i = 0; i < 100; ++i) {
        _sleep(200);
        printf("customPThreadMethod  i = %d\n",i);
    }
    
    
    return 0;   //必须返回,如果不返回,会出现很奇怪的问题
}

int main(){

    /**
     * PTW32_DLLPORT int PTW32_CDECL pthread_create (pthread_t * tid,   //1、线程Id,注意这里是需要一个指针
                            const pthread_attr_t * attr,    //2、线程属性 指针
                            void *(PTW32_CDECL *start) (void *),    //3、函数指针----->异步任务
                            void *arg);     //4、函数指针的参数传递 指针
     */

    pthread_t pthreadId;    //线程ID
    int value01 = 8877;
    // &value   是取内存地址
    // *value   是取内存地址所对应的值
    // TODO &pthreadId 线程Id的内存地址,因为需要传递的参数是个线程id的指针
    // TODO &value01 函数指针的参数,相当于传递给我们自定义的customPThreadMethod这个函数指针的异步任务的参数  (void *);而这个参数是需要任意类型的指针,这里就传递了一个 内存地址
    pthread_create(&pthreadId,0,customPThreadMethod,&value01);
    // pthread_join 会等待耗时任务执行完成之后,才会执行下面的代码;
    //   pthread_join(pthreadId,0);
    printf("线程执行完毕\n");
    return 0;
}

线程升级使用

pthread_create方式创建 ,毋庸置疑;
这里我们用到了简单的线程属性,通过pthread_attr_init进行初始化,注意初始化出来后一定要记得回收;pthread_attr_destroy来进行回收;
通过pthread_attr_setdetachstate,给线程属性,设置分离线程、非分离线程;
什么是 分离线程 和 非分离线程?
答:非分离线程,要等到耗时任务(异步任务)全部执行完毕后,才会执行join后面关联的代码逻辑;
分离线程,就是各玩各的,不管异步任务是否执行完毕,该结束就结束

pthread_join:也就是非分离线程,可以不设置线程属性,直接用这个,来设置非分离线程;

#include <iostream>
#include <string>
// TODO 第四步:导入 线程的 头文件
#include <pthread.h>

using namespace std;

// TODO 线程升级使用
void * customPThreadMethod2(void * pVoid){
    for (int i = 0; i < 100; ++i) {
        _sleep(200);
        printf("customPThreadMethod2  i = %d\n",i);
    }
    return 0;   //必须返回,如果不返回,会出现很奇怪的问题
}

int main(){
	pthread_t pthreadId;    //线程ID
    pthread_attr_t pthreadAttr; //线程属性,不允许有野指针,需要初始化才不会报错
    //初始化 线程属性
    pthread_attr_init(&pthreadAttr);
    float value = 0.88;

    // 什么是 分离线程 和 非分离线程?
    // 答:非分离线程,要等到耗时任务(异步任务)全部执行完毕后,才会执行join后面关联的代码逻辑
    //     分离线程,就是各玩各的,不管异步任务是否执行完毕,该结束就结束
    // pthread_attr_setdetachstate(&pthreadAttr,PTHREAD_CREATE_DETACHED);  //开启分离线程,pthread_join效果丢失
    pthread_attr_setdetachstate(&pthreadAttr,PTHREAD_CREATE_JOINABLE);  //开启非分离线程,和pthread_join 关联起来了

    pthread_create(&pthreadId,&pthreadAttr,customPThreadMethod2,&value);
    // pthread_join 会等待耗时任务执行完成之后,才会执行下面的代码;
    pthread_join(pthreadId,0);
    //线程属性,必须要回收
    pthread_attr_destroy(&pthreadAttr);
    printf("线程执行完毕\n");
    return 0;
}

线程安全(互斥锁)

我这里测试通过多个线程对队列进行删除和读取操作,来玩这个互斥锁。在加上互斥锁的前提下,判断队列是否为空,不为空的情况向下,读取第一个值,然后进行删除。

线程锁:互斥锁pthread_mutex_t,互斥锁 不能是野指针,否则会出错,所以需要初始化,通过pthread_mutex_init方法进行初始化还有初始化了,记得要进行回收,互斥锁的回收pthread_mutex_destroy;
pthread_mutex_lock:线程中加上互斥锁;
pthread_mutex_unlock:线程中解除互斥锁;

#include <iostream>
#include <string>
// TODO 第四步:导入 线程的 头文件
#include <pthread.h>

#include <queue>

using namespace std;
// TODO 线程的升级,学习 线程的安全问题 互斥锁=====START========
// 定义一个全局的队列,用于存储使用
queue<int> saveAllData;
// 定义一个互斥锁
pthread_mutex_t mutex;  //互斥锁 不能是野指针,否则会出错
void *  customPThreadMethod3(void *pVoid){
    // 10条线程,同时运行,并且对队列的数据 进行操作,还要保证数据的安全 (加锁,防止数据错乱)
    //加上互斥锁
    pthread_mutex_lock(&mutex);
    printf("当前线程标记是======%d\n",*static_cast<int *>(pVoid));
    //队列不为空的时候
    if(!saveAllData.empty()){
        printf("获取到的队列数据:%d\n",saveAllData.front());
        saveAllData.pop();//把数据弹出去(也就是删除掉)
    }else{
        printf("队列中没有数据了\n");
    }
    //解除锁定,是为了让其他多线程,可以进来操作,这就是解锁的目的
    pthread_mutex_unlock(&mutex);
    return 0;
}

// TODO 线程的升级,学习 线程的安全问题 互斥锁=====END========

int main(){
//初始化互斥锁,初始化就一定要回收
    pthread_mutex_init(&mutex,NULL);

    //给队列初始化数据
    for (int j = 1000; j < 1008; ++j) {
        saveAllData.push(j);
    }
    //初始化一个数组  线程id数组,10个
    pthread_t pthreadIdArr[10];
    for (int i = 0; i < 10; ++i) {
        //创建线程
        pthread_create(&pthreadIdArr[i],0,customPThreadMethod3,&i);
    }
    printf("执行  线程下面的方法\n");


    system("pause");    //让 main函数,不要去结束,为了测试多线程
    //回收互斥锁
    pthread_mutex_destroy(&mutex);
// TODO 线程的升级,学习 线程的安全问题 互斥锁========end==========
    return 0;
}

线程安全(小例子)

这里通过消费者和生产者来玩个小例子,生产者生产数据,消费者拿取数据,如果生产者还没生产出来的时候,消费者进行等待;

工具类SafeQueueClass

模板的方式,也就是java里面的泛型
通过泛型队列来充当生产者生产出来数据;
构造函数中,进行初始化互斥锁和条件变量;
析构函数中,进行回收互斥锁和条件变量;
add函数:生产出来的数据,加入到队列;
get函数:消费者读取数据,从队列中进行获取数据,然后删除该数据;
生产和消费,用同一个互斥锁进行锁定,生产出来之后得告诉消费者,也就是唤醒消费者,pthread_cond_broadcast;
消费函数中,判空队列,如果队列为空的时候,我们进行调用pthread_cond_wait方法来进行休眠,休眠的状态下,消费者通过pthread_cond_broadcast方法来唤醒;

// TODO  生产者 消费者 工具类

#ifndef NDK06_CPP_SAFE_QUEUE_TOOLS_H
#define NDK06_CPP_SAFE_QUEUE_TOOLS_H

#endif //NDK06_CPP_SAFE_QUEUE_TOOLS_H

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

using namespace std;

// 定义一个模板(相当于java中的泛型)
template <typename T>

class SafeQueueClass{
private:
    queue<T> queue;    //定义队列
    pthread_mutex_t  mutex;     //定义互斥锁,为了线程安全处理  不允许野指针,所以必须初始化
    pthread_cond_t cond;    //定义条件变量,为了实现,等待 读取 的功能     不允许野指针,所以必须初始化
public:
    SafeQueueClass(){   //构造函数
        //初始化互斥锁
        pthread_mutex_init(&mutex,NULL);
        //初始化 条件变量
        pthread_cond_init(&cond,NULL);
    }

    ~SafeQueueClass(){  //析构函数,对象被回收时自动调用
        //回收 互斥锁
        pthread_mutex_destroy(&mutex);
        //回收 条件变量
        pthread_cond_destroy(&cond);
    }
    // TODO 加入队列中 (进行生产)
    void add(T t){
        //开始锁定,为了保证同步的安全性
        pthread_mutex_lock(&mutex);
        //队列添加值
        queue.push(t);
        //告诉消费者,这里生产成功了
//        pthread_cond_signal(&cond); //由系统去唤醒一个线程,相当于java中的 notify
        pthread_cond_broadcast(&cond);  //唤醒通知所有线程,相当于java中的 notifyAll
        //解锁
        pthread_mutex_unlock(&mutex);
    }
    //TODO 从队列中获取(进行消费)
    void get(T& t){ //引用类型的值传进来
        //开始锁定,为了保证同步的安全性
        pthread_mutex_lock(&mutex);
        //用if可能有问题,可能会被系统唤醒,所以这里用while
        while (queue.empty()){  //如果队列中为空,也就是没有数据可以消费
            //这里进行等待,条件变量,完成等待
            pthread_cond_wait(&cond,&mutex);   //通过pthread_cond_broadcast(&cond) 来进行唤醒
        }
        //证明被唤醒了,也就是有数据了,front取出首元素,交给引用类型参数;
        // TODO 引用类型的参数进行赋值,外面传递进来的参数就会跟着变
        t = queue.front();
        queue.pop();    //把数据弹出去,等于删除掉

        //解锁
        pthread_mutex_unlock(&mutex);
    }

};

main函数

C里面 Boolean类型,非0即是true;
创建了两个分离线程(pthread_join),一个生产者,一个消费者,线程任务中都是死循环;
非分离线程,要等到耗时任务(异步任务)全部执行完毕后,才会执行join后面关联的代码逻辑;
注意:我们这里模拟生产者就用了 控制台用户输入的方式:cin >> value;进行生产取值;
如果输入的是-1的情况下,生产和消费者都会跳出死循环,结束线程任务;
注意:DDDDDDDDDDDDDD的打印,两个线程任务都执行完成后,才会打印这句话

//
// Created by kaizi on 2021/2/22.
//

#pragma once

#include <iostream>
#include <string>
// 导入工具类的头文件
#include "safe_queue_tools.h"

using namespace std;

SafeQueueClass<int> safeQueueClass;

// 模拟生产者
void * addMethod(void *){
    while (1) {   //死循环,非0即是true
        printf("addMethod\n");
        printf("请输入数据\n");
        int value;
        cin >> value;   //控制台输入的信息,赋值到value变量上面去
        safeQueueClass.add(value);
        if(-1 == value){
            printf("生产者停止生产\n");
            break;
        }
    }
    return 0;
}

// 模拟消费者
void * getMethod(void *){
    while (1){   //死循环,非0即是true
        printf("getMethod\n");
        int value;
        safeQueueClass.get(value);
        printf("消费者得到的数据:%d\n",value);

        //你只要输入一个-1,我们就结束当前循环
        if(-1==value){
            printf("消费者不能再消费了,因为生产者的已经停止生产了\n");
            break;
        }
    }
    return 0;
}

// TODO 测试  生产者 消费者的工具类

int main(){
    // TODO 创建线程
    /**
  * PTW32_DLLPORT int PTW32_CDECL pthread_create (pthread_t * tid,   //1、线程Id,注意这里是需要一个指针
                         const pthread_attr_t * attr,    //2、线程属性 指针
                         void *(PTW32_CDECL *start) (void *),    //3、函数指针----->异步任务
                         void *arg);     //4、函数指针的参数传递 指针
  */
    pthread_t pthreadId_get;    //线程id--消费者任务的
    pthread_create(&pthreadId_get,0,getMethod,0);

    pthread_t pthreadId_add;    //线程id--生产者任务的
    pthread_create(&pthreadId_add,0,addMethod,0);

    //需要用非分离线程来实现
    // TODO 非分离线程,要等到耗时任务(异步任务)全部执行完毕后,才会执行join后面关联的代码逻辑
    pthread_join(pthreadId_get,0);  //消费者
    cout << "DDDDDDDDDDDDDD" << endl;   //两个线程任务都执行完成后,才会打印这句话
    pthread_join(pthreadId_add,0);  //生产者

    cout << "耗时任务执行完成了" << endl;

    return 0;
}


辛苦各位童鞋观看到最后,如果博客中有不对的地方望指出,大神勿喷,谢谢~~ 一起学习和进步

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值