线程简单使用
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;
}
辛苦各位童鞋观看到最后,如果博客中有不对的地方望指出,大神勿喷,谢谢~~ 一起学习和进步