C++: C++11 成员函数作为pthread线程 (转)
这种方式似乎也可以:https://www.cnblogs.com/diegodu/p/4655036.html
我自己也觉得这个标题讲得云里雾里的。事情是这样的:很多时候,我们希望一个class的某个成员函数能够作为一个线程来执行,就像Python中的threading库,只要把Thread()方法的target参数指向一个成员方法,比如self.__run,那么self.__run方法就会成为一个线程执行代码段。而pthread_create的原型是这样的:
int pthread_create(pthread_t *thread,pthread_attr_t *attr,void *(*routine)(void *),void *arg);
注意第三个参数routine是一个普通函数,而不能是一个成员函数。这不是废话嘛,不是普通函数怎么传进去。虽然很清晰,但是有时会破坏面向对象的思想。比如说Python中这么一段逻辑:
import threading import time class A: def __init__(self,name): self.__name=name self.__count=0 self.__running=True self.__thread=threading.Thread(target=self.__run) self.__thread.start() def count(self): return self.__count def stop(self): self.__running=False self.__thread.join() def __run(self): while self.__running: print(self.__name) self.__count+=1 time.sleep(1) a=A('zjs') time.sleep(10) a.stop() print(a.count())
很显然,因为成员方法可以作为线程体来执行,所以获得了如下好处:
- 线程的变量传递非常方便,直接读写成员变量即可;
- 线程体作为对象的一部分,可以访问对象的私有变量和方法。
在C++11之前,如果要实现类似逻辑,一种写法是这样的:
A.h
#ifndef A_H #define A_H #include <pthread.h> class A { public: A(const char* name); int count(); void stop(); //不得不暴露 public: const char* m_name; int m_count; bool m_running; private: pthread_t m_thread; }; #endif
A.cpp
#include "A.h" #include <stdio.h> #include <unistd.h> static void* __run(void* arg) { A* a=(A*)arg; while(a->m_running) { printf("%s\n",a->m_name); a->m_count++; sleep(1); } } A::A(const char* name) { m_name=name; m_count=0; m_running=true; pthread_create(&m_thread,0,__run,this); } int A::count() { return m_count; } void A::stop() { m_running=false; pthread_join(m_thread,0); }
testA.cpp
#include "A.h" #include <stdio.h> #include <unistd.h> int main() { A a("zjs"); sleep(10); a.stop(); printf("%d\n",a.count()); return 0; }
注意到之所以需要把A的三个成员变量设为public,是为了能够让__run()能够访问到。但这就破坏了封装,使得这三个变量对外暴露。为了提高封装性,另一种好一些的办法是这样的:
A.h
#ifndef A_H #define A_H #include <pthread.h> class A { public: A(const char* name); int count(); void stop(); //授权__A_run()能够访问私有变量 friend void* __A_run(void* arg); private: const char* m_name; int m_count; bool m_running; pthread_t m_thread; }; #endif
A.cpp
#include "A.h" #include <stdio.h> #include <unistd.h> void* __A_run(void* arg) { A* a=(A*)arg; while(a->m_running) { printf("%s\n",a->m_name); a->m_count++; sleep(1); } } A::A(const char* name) { m_name=name; m_count=0; m_running=true; pthread_create(&m_thread,0,__A_run,this); } int A::count() { return m_count; } void A::stop() { m_running=false; pthread_join(m_thread,0); }
testA.cpp
#include "A.h" #include <stdio.h> #include <unistd.h> int main() { A a("zjs"); sleep(10); a.stop(); // 然而__A_run()本身对外暴露了 //__A_run(&a); printf("%d\n",a.count()); return 0; }
可以看到,通过友元函数,可以使得线程体能够访问私有成员了。但是呢,友元函数本身是extern的,使得__A_run()本身对外暴露了,这使得外界可以手动调用__A_run,有一定的风险。不过函数暴露总归比变量暴露好得多。在C++11之前,封装性最好的办法,可能也就只能是这样了:在A.cpp中定义一个结构体,该结构体拥有和A一样的内存布局,并且A中成员变量连续分布,然后把A中第一个成员变量的地址传给线程体。比如这样:
A.h
#ifndef A_H #define A_H #include <pthread.h> class A { public: A(const char* name); int count(); void stop(); private: const char* m_name; int m_count; bool m_running; pthread_t m_thread; }; #endif
A.cpp
#include "A.h" #include <stdio.h> #include <unistd.h> // 内存布局和A一样 struct A_vars { const char* m_name; int m_count; bool m_running; }; static void* __run(void* arg) { struct A_vars* a=(struct A_vars*)arg; while(a->m_running) { printf("%s\n",a->m_name); a->m_count++; sleep(1); } } A::A(const char* name) { m_name=name; m_count=0; m_running=true; //第一个变量的地址传过去 pthread_create(&m_thread,0,__run,&m_name); //pthread_create(&m_thread,0,__run,this); } int A::count() { return m_count; } void A::stop() { m_running=false; pthread_join(m_thread,0); }
这样做,对外接口确实完美了,但是!太依赖底层的细节了,如果编译器对内存分布进行了优化,或者A有虚表,这种方法很可能就爆炸了!换句话说,通过牺牲稳定性、可移植性来换取封装性,貌似不太可取。
好在C++ 11中出现了匿名函数,使得函数能够嵌套定义。
A.h
#ifndef A_H #define A_H #include <pthread.h> class A { public: A(const char* name); int count(); void stop(); private: void run(); private: const char* m_name; int m_count; bool m_running; pthread_t m_thread; }; #endif
A.cpp
#include "A.h" #include <stdio.h> #include <unistd.h> A::A(const char* name) { m_name=name; m_count=0; m_running=true; pthread_create(&m_thread,0, [](void* arg) { A* a=(A*)arg; a->run(); return (void*)0; } ,this); } int A::count() { return m_count; } void A::stop() { m_running=false; pthread_join(m_thread,0); } void A::run() { while(m_running) { printf("%s\n",m_name); m_count++; sleep(1); } }
代码一下子完美啦~~
编译A.cpp的时候,要这样:
g++ -std=gnu++11 -c A.cpp -o A.o
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库