当前位置: 首页 > news >正文

【Linux】多线程创建及封装

 本篇主要介绍多线程,以及线程的封装,线程控制的相关介绍在【Linux】线程控制

1.创建和等待多线程

我们可以一次创建十个线程,创建的时候直接用for循环创建,参数就传这些线程的名字,然后把这些线程的ID保存在vector里,ID的类型pthread_t其实就是long int类型。

#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <vector>
#include <string>const int num = 10;void* ThreadFunc(void* args)
{std::string name = static_cast<const char*>(args);while(true){std::cout << name << "运行中..." << std::endl;sleep(1);}return nullptr;
}int main()
{std::vector<long long> tids; for(int i = 0; i < num; i++) //创建10个线程{char name[64];snprintf(name, sizeof(name), "新线程%d", i); //格式化名字pthread_t tid;int n = pthread_create(&tid, nullptr, ThreadFunc, name);if(n == 0)tids.push_back(tid); //创建成功就把新线程的ID存着elsecontinue;sleep(1);}return 0;
}

然后用监控脚本看一下线程。

 while :; do ps -aL ; sleep 1; done

在等待的时候,通过我们保存在vector里的线程ID一个一个等待。

#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <vector>
#include <string>const int num = 10;void *ThreadFunc(void *args)
{std::string name = static_cast<const char *>(args);int cnt = 5;while (cnt--){std::cout << name << "运行中..." << std::endl;sleep(1);}return nullptr;
}int main()
{std::vector<long long> tids;for (int i = 0; i < num; i++) // 创建10个线程{char name[64];snprintf(name, sizeof(name), "新线程%d", i); // 格式化名字pthread_t tid;int n = pthread_create(&tid, nullptr, ThreadFunc, name);if (n == 0)tids.push_back(tid); // 创建成功就把新线程的ID存着elsecontinue;sleep(1);}for (int i = 0; i < num; i++) //一个一个等待{int n = pthread_join(tids[i], nullptr);if (n == 0)std::cout << "等待成功" << std::endl;elsestd::cout << "等待失败" << std::endl;}return 0;
}

但是我们如果在创建线程时for循环里不加sleep,而在ThreadFunc函数最开始加上sleep,会出现如下结果,只有新线程9。

const int num = 10;void *ThreadFunc(void *args)
{sleep(1);std::string name = static_cast<const char *>(args);int cnt = 5;while (cnt--){std::cout << name << "运行中..." << std::endl;sleep(1);}return nullptr;
}int main()
{std::vector<long long> tids;for (int i = 0; i < num; i++) // 创建10个线程{char name[64];snprintf(name, sizeof(name), "新线程%d", i); // 格式化名字pthread_t tid;int n = pthread_create(&tid, nullptr, ThreadFunc, name);if (n == 0)tids.push_back(tid); // 创建成功就把新线程的ID存着elsecontinue;//sleep(1);}return 0;
}

因为name是for循环里的一个临时数组,创建线程的时候传递过去的name是这个数组的起始地址,每一次for循环,编译器都会在同一个栈帧的固定偏移量位置上开辟name的空间,就导致每次for循环不停的开辟、释放、开辟、释放...但是每次开辟到的都是同一个位置,所以给pthread_create传过去的name起始地址就是同样的。

ThreadFunc函数拿到这个值之后sleep一秒才将ThreadFunc里的name改动。

所以就发生了ThreadFunc里的name还没来得及改动,创建线程的for循环就进入下一个循环了,里面的name的内容就被修改了,意思就是指针没变,但是指针指向的内容已经改了

所以这里我们要做如下改动,在堆区给name开辟空间,这样每个线程得到的name就不是一样的了。

#include "Thread.hpp"const int num = 10;void *ThreadFunc(void *args)
{sleep(1);std::string name = static_cast<const char *>(args);int cnt = 5;while (cnt--){std::cout << name << "运行中..." << std::endl;sleep(1);}return nullptr;
}int main()
{std::vector<long long> tids;for (int i = 0; i < num; i++) // 创建10个线程{//char name[64]; //错误写法char* name = new char[64]; //正确写法,堆区开辟空间snprintf(name, 64, "新线程%d", i); // 格式化名字pthread_t tid;int n = pthread_create(&tid, nullptr, ThreadFunc, name);if (n == 0)tids.push_back(tid); // 创建成功就把新线程的ID存着elsecontinue;}for (int i = 0; i < num; i++) //一个一个等待{int n = pthread_join(tids[i], nullptr);if (n == 0)std::cout << "等待成功" << std::endl;elsestd::cout << "等待失败" << std::endl;}return 0;
}

2.线程封装

先做一下准备工作。

//Thread.hpp文件
#ifndef _THREAD_H_
#define _THREAD_H_#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <vector>
#include <string>namespace MyThread 
{class Thread{public:private:};
}#endif
//Main.cc文件
#include "Thread.hpp"int main()
{return 0;
}
//Makefile
thread:Main.ccg++ -o $@ $^ -lpthread.PHONY:clean
clean:rm -f thread

我们需要设计一下线程的名字,用一个变量代表被创建的线程编号,类成员变量如下。

namespace MyThread
{static int num = 1; class Thread{public:Thread(): _tid(0),_isdetach(false), /*默认不分离*/_isrunning(false){_name = "new thread-" + std::to_string(num++);}private:pthread_t _tid; //线程IDstd::string _name; //线程的名字bool _isdetach; //线程是否设置分离状态bool _isrunning; //线程是否启动};
}

2.1 线程的启动和分离

设计一个Start接口,启动线程。

bool Start()
{int n = pthread_create(&_tid, nullptr, ThreadFunc, nullptr/*暂时先不传参数*/);if(n != 0) //创建失败{std::cerr << "create tread error: " << strerror(n) << std:: endl;return false;}//创建成功}

这里还要考虑线程分离的情况,线程的分离状态可以在线程启动之前就设置,也可以是线程已经启动了,再设置成分离状态。

private:void SetRun(){_isrunning = true;}public:void Detach(){if (_isdetach) // 如果已经分离了,直接返回return;if (_isrunning)           // 如果线程此时正在运行pthread_detach(_tid); // 分离线程// 如果此时还没运行,就只设置标志位,Start函数内会调用DetachSetDetach();}void SetDetach(){std::cout << "线程被分离" << std::endl;_isdetach = true;}bool Start(){int n = pthread_create(&_tid, nullptr, ThreadFunc /*暂时未实现*/, nullptr /*暂时先不传参数*/);if (n != 0) // 创建失败{std::cerr << "create tread error: " << strerror(n) << std::endl;return false;}// 线程启动前设置分离if (_isdetach)Detach();SetRun(); // 设置状态为run}

Detach这样实现就可以保证不管是启动前分离还是启动之后再分离都可以,启动前调用Detach函数,两个if都不满足,就只会设置_isdetach 标志位为false,启动后调用Detach,线程分离并且修改标志位。

2.2 线程停止和等待

取消一个线程就是要确保他是在运行的,取消进程之后,标志位也要更新。

    bool Stop(){if (_isrunning){int n = pthread_cancel(_tid);if (n != 0){std::cerr << "cancel tread error: " << strerror(n) << std::endl;return false;}else{_isrunning = false;std::cout << _name << "stop success" << std::endl;}}return true;}

等待进程不管是不是在运行都可以等待,但是如果被分离了就不用等待了,线程等待的结果我们也可以获取一下。

    bool Join(){if (_isdetach){std::cout << "线程已经被分离,join失败" << std::endl;return false;}int n = pthread_join(_tid, &_ret);if (n != 0){std::cerr << "join tread error: " << strerror(n) << std::endl;return false;}else{std::cout << "join success" << std::endl;}return true;}
private:pthread_t _tid;    // 线程IDstd::string _name; // 线程的名字bool _isdetach;    // 线程是否设置分离状态bool _isrunning;   // 线程是否启动void *_ret;        // 线程等待的结果

2.3 新线程入口函数

我们新线程的入口函数不能在类内实现,因为含函数要求参数为void*,但是在类内部的函数第一个参数是隐藏的this指针!!所以这个ThreadFunc函数其实是传了两个参数。

解决方法有很多,这里我选择将ThreadFunc函数设为static,这样就没有this指针了。

static void* ThreadFunc(void* args)
{}

但是我们并不是为了让线程只执行类内的ThreadFunc,我们创建线程是为了给线程指派任务的,所以任务必须是类外提供的方法。

#include <functional>class Thread
{using func_t = std::function<void()>; //返回值为void,参数为空//...
}

意思就是定义一个函数类型func_t,让线程未来执行我所指定的方法,所以这里类成员变量还要添加一个。

class Thread
{using func_t = std::function<void()>; // 返回值为void,参数为空public:Thread(func_t func): _tid(0),_isdetach(false), /*默认不分离*/_isrunning(false),_ret(nullptr),_func(func){_name = "new thread-" + std::to_string(num++);}//...private:pthread_t _tid;    // 线程IDstd::string _name; // 线程的名字bool _isdetach;    // 线程是否设置分离状态bool _isrunning;   // 线程是否启动void *_ret;        // 线程等待的结果func_t _func;      // 线程执行的方法
}

所以现在我们创建的线程不是为了执行对应的ThreadFunc函数的,而是让这个线程执行我给他指派的任务。此时我们的ThreadFunc函数里就要对_func进行回调。

    static void *ThreadFunc(void *args){_func(); //回调处理return nullptr; // 返回值不关心}bool Start(){if (_isrunning)return false; // 线程已经启动了就不能重复启动int n = pthread_create(&_tid, nullptr, ThreadFunc, nullptr /*暂时先不传参数*/);if (n != 0) // 创建失败{std::cerr << "create tread error: " << strerror(n) << std::endl;return false;}// 线程启动前设置分离if (_isdetach)Detach();SetRun(); // 设置状态为runreturn true;}

但是!这样写是无法调用回调的,因为ThreadFunc函数此时是static的,没有this指针,也就无法访问当前类成员属性。所以这里ThreadFunc函数传参就要传this指针。

   static void *ThreadFunc(void *args) {Thread *self = static_cast<Thread *>(args);self->_func(); //回调处理return nullptr; // 返回值不关心}bool Start(){if (_isrunning)return false; // 线程已经启动了就不能重复启动int n = pthread_create(&_tid, nullptr, ThreadFunc, this); //...}

这样ThreadFunc函数就可以在类内访问回调方法了。

并且我们前面的分离和运行的标志位逻辑可以放在ThreadFunc函数,这样代码的可读性更好。

    static void *ThreadFunc(void *args){Thread *self = static_cast<Thread *>(args);self->SetRun();      // 设置状态为runif (self->_isdetach) // 设置分离self->Detach();self->_func();  // 回调处理return nullptr; // 返回值不关心}bool Start(){if (_isrunning)return false; // 线程已经启动了就不能重复启动int n = pthread_create(&_tid, nullptr, ThreadFunc, this);if (n != 0) // 创建失败{std::cerr << "create tread error: " << strerror(n) << std::endl;return false;}else{std::cout << _name << "create success" << std::endl;}return true;}

2.4 测试

#include "Thread.hpp"
using namespace MyThread;int main()
{Thread t();return 0;
}

t后面的括号里就要传要执行的方法,这里可以用Lambda表达式。

int main()
{Thread t([](){while(true){std::cout << "新线程运行中..." << std::endl;sleep(1);} }); // 返回值为void,参数为空return 0;
}

此时就由这个Lambda表达式来初始化_func,然后线程对象就被创建出来了。

int main()
{Thread t([](){while(true) {std::cout << "新线程运行中..." << std::endl;sleep(1);} }); // 返回值为void,参数为空t.Start();sleep(3);t.Stop();sleep(3);t.Join();sleep(3);return 0;
}

我们还可以设置Detach,先测试启动前分离。

int main()
{Thread t([](){while(true) {std::cout << "新线程运行中..." << std::endl;sleep(1);} }); // 返回值为void,参数为空t.Detach();    // 设置分离状态t.Start();sleep(3);t.Stop();sleep(3);t.Join();sleep(3);return 0;
}

再测试一个启动后再分离线程。

int main()
{Thread t([](){while(true) {std::cout << "新线程运行中..." << std::endl;sleep(1);} }); // 返回值为void,参数为空t.Start();t.Detach(); // 设置分离状态sleep(3);t.Stop();sleep(3);t.Join();sleep(3);return 0;
}

 2.5 设置和获取线程名称

  • pthread_setname_np pthread_getname_np 是两个⽤于设置和获取线程名称的⾮标准函数(_np 表⽰ "non-portable",即⾮可移植的)。它们通常在 Linux 和 其他⼀些类 Unix 系统中可⽤,⽤于调试和多线程程序的管理。

pthread_setname_np就是传一个线程ID,给这个线程设置个名字,pthread_getname_np就是可以获取指定线程设置的名字。

static void *ThreadFunc(void *args)
{Thread *self = static_cast<Thread *>(args);self->SetRun();     if (self->_isdetach) self->Detach();pthread_setname_np(self->_tid, self->_name.c_str()); //设置名字self->_func(); return nullptr; 
}
#include "Thread.hpp"
using namespace MyThread;int main()
{Thread t([](){while(true) {char buffer[64];pthread_getname_np(pthread_self(), buffer, sizeof(buffer)); //获取名字std::cout << buffer << "运行中..." << std::endl;sleep(1);} }); // 返回值为void,参数为空t.Start();t.Detach(); // 设置分离状态sleep(3);t.Stop();sleep(3);t.Join();sleep(3);return 0;
}

  • 线程名称⻓度限制: 在 Linux 上,线程名称的最⼤⻓度为 16 个字符(包括结尾的 \0 ),如果名称超过这个⻓度,会被截断。
  • 权限: 通常,只有线程⾃⾝可以设置⾃⼰的名称。尝试设置其他线程的名称可能会导致错误。

其实就是把pthread_setname_np设置的名字写入到了线程的局部存储,get的时候就从局部存储里取,局部存储只有线程自己能访问。

下面是Tread.hpp的代码。

#ifndef _THREAD_H_
#define _THREAD_H_#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <vector>
#include <cstring> //strerror要包含的头文件
#include <string>
#include <functional>namespace MyThread
{static int num = 1;class Thread{using func_t = std::function<void()>; // 返回值为void,参数为空private:void SetRun(){_isrunning = true;}void SetDetach(){std::cout << "线程被分离" << std::endl;_isdetach = true;}static void *ThreadFunc(void *args){Thread *self = static_cast<Thread *>(args);self->SetRun();      // 设置状态为runif (self->_isdetach) // 设置分离self->Detach();pthread_setname_np(self->_tid, self->_name.c_str()); //设置名字self->_func();  // 回调处理return nullptr; // 返回值不关心}public:Thread(func_t func): _tid(0),_isdetach(false), /*默认不分离*/_isrunning(false),_ret(nullptr),_func(func){_name = "new thread-" + std::to_string(num++);}void Detach(){if (_isdetach) // 如果已经分离了,直接返回return;if (_isrunning)           // 如果线程此时正在运行pthread_detach(_tid); // 分离线程// 如果此时还没运行,就只设置标志位,Start函数内会调用DetachSetDetach();}bool Start(){if (_isrunning)return false; // 线程已经启动了就不能重复启动int n = pthread_create(&_tid, nullptr, ThreadFunc, this);if (n != 0) // 创建失败{std::cerr << "create tread error: " << strerror(n) << std::endl;return false;}else{std::cout << _name << " create success" << std::endl;}return true;}bool Stop(){if (_isrunning){int n = pthread_cancel(_tid);if (n != 0){std::cerr << "cancel tread error: " << strerror(n) << std::endl;return false;}else{_isrunning = false;std::cout << _name << " stop success" << std::endl;}}return true;}bool Join(){if (_isdetach){std::cout << "线程已经被分离,join失败" << std::endl;return false;}int n = pthread_join(_tid, &_ret);if (n != 0){std::cerr << "join tread error: " << strerror(n) << std::endl;return false;}else{std::cout << "join success" << std::endl;}return true;}~Thread(){}private:pthread_t _tid;    // 线程IDstd::string _name; // 线程的名字bool _isdetach;    // 线程是否设置分离状态bool _isrunning;   // 线程是否启动void *_ret;        // 线程等待的结果func_t _func;      // 线程执行的方法};
}#endif

3.带模板参数的封装

namespace MyThread
{static int num = 1;template <typename T> // 模板class Thread{using func_t = std::function<void(T)>; // 返回值为void,参数为类型为Tprivate://...static void *ThreadFunc(void *args){Thread<T> *self = static_cast<Thread<T> *>(args); //全部改为模板self->SetRun();      if (self->_isdetach) self->Detach();self->_func(self->_data);  // 回调处理,参数为Treturn nullptr; }public:Thread(func_t func, T data) : _tid(0),_isdetach(false), _isrunning(false),_ret(nullptr),_func(func),_data(data)  //这个参数就是回调函数的参数{_name = "new thread-" + std::to_string(num++);}//...private:pthread_t _tid;    // 线程IDstd::string _name; // 线程的名字bool _isdetach;    // 线程是否设置分离状态bool _isrunning;   // 线程是否启动void *_ret;        // 线程等待的结果func_t _func;      // 线程执行的方法T _data;           // 传给执行方法的参数};
}#endif

除了上面给出来的接口要做改变,其他接口都不变。

#include "Thread.hpp"
using namespace MyThread;void Conter(int num)
{while (num--){std::cout << "新进程运行中..., " << num << std::endl;sleep(1);}
}int main()
{int cnt = 5;Thread<int> t(Conter, cnt); // cnt是Conter的参数t.Start();t.Join();return 0;
}

这里的cnt是Conter的参数,就类似于pthread_create函数的第四个参数是第三个参数的参数。

现在我们可以传int类型的参数 ,当然也可以传自定义类型,这里就不演示了。

4.多线程

多线程很简单,代码都不用改,就是在Main.cc里直接for循环创建。

#include "Thread.hpp"
#include <vector>
using namespace MyThread;int main()
{std::vector<Thread> threads;for (int i = 0; i < 5; i++){Thread t([](){while(true) {char buffer[64];pthread_getname_np(pthread_self(), buffer, sizeof(buffer)); //获取名字std::cout << buffer << "运行中..." << std::endl;sleep(1);} }); // 返回值为void,参数为空threads.emplace_back(t);}for (auto &t : threads){t.Start();}sleep(3); //3秒后全部停止并回收for (auto &t : threads){t.Stop();}for (auto &t : threads){t.Join();}return 0;
}

本篇分享就到这里了,我们下篇见~

http://www.dtcms.com/a/438114.html

相关文章:

  • 苏州网站推广公司创业商机网餐饮
  • unity 读取PPT显示到屏幕功能
  • Django - 让开发变得简单高效的Web框架
  • C# 判断语句详解
  • 新建一个网站需要多少钱舟山专业做网站
  • JVM中的垃圾回收机制
  • 【计算机视觉】概述
  • 【第五章:计算机视觉-项目实战之生成对抗网络实战】2.基于SRGAN的图像超分辨率实战-(2)实战1:DCGAN模型搭建
  • 【精品资料鉴赏】大型企业网络安全整体解决方案
  • 重庆建设医院官方网站wordpress中文社区
  • [优选算法专题三.二分查找——NO.23搜索旋转排序数组中的最⼩值]
  • 【个人修养】商务礼仪教程
  • 报告派研读:2025年全球PC/主机游戏洞察报告
  • 用jquery做的书籍网站discuz做服务网站
  • Linux 驱动开发入门:LCD 驱动与内核机制详解
  • [Linux基础——Lesson9.调试器GDB]
  • 网站 推送中国万网域名官网
  • 主窗口(QMainWindow)如何放入文本编辑器(QPlainTextEdit)等继承自QWidget的对象--(重构版)
  • 和 AI 一起修 Bug 心得体会
  • 网站建设科技公司外部环境分析网站首页没有权重
  • 【大语言模型】—— Transformer的QKV及多头注意力机制图解解析
  • SYN VISION韩国发布会:获评非小号Alpha,战略合作PrompTale
  • 安徽工程建设造价信息网站html网站开发视频
  • 图书馆自习室|基于SSM的图书馆自习室座位预约小程序设计与实现(源码+数据库+文档)
  • Ollama 使用详解:本地部署大语言模型的指南
  • 手机上哪个网站浙江省嘉兴市建设局网站
  • 秒杀系统崩溃?Redis分片+Sentinel熔断架构设计指南
  • 【开题答辩全过程】以 J2EE技术在在线购物分享应用中的应用为例,包含答辩的问题和答案
  • 【深入理解计算机网络03】计算机网络的分层结构,OSI模型与TCP/IP模型
  • 网站怎么做移动的窗口达州市建设规划网站