第四节 面向对象编程和基于对象编程
4.1 面向对象编程封装多线程
注意:pthread非
linux系统的默认库, 需手动链接-线程库 -
lpthread
示例代码:
CMakeList
cmake_minimum_required(VERSION 3.21)
project(test)
set(CMAKE_CXX_STANDARD 11)
add_executable(test main.cpp Thread.cpp)
target_link_libraries(test pthread)#只需要库的名称
Thread.h
#ifndef TEST_THREAD_H
#define TEST_THREAD_H
#include <pthread.h>
class Thread {
public:
Thread();
virtual ~Thread();
void Start();
void Join();
void SetAutoDelete(bool autoDelete);
private:
static void* ThreadRoutine(void *arg);//共享一个成员函数
virtual void Run()=0;
pthread_t mThreadId_;
bool autoDelete_;
};
#endif //TEST_THREAD_H
Threap.cpp
#include <iostream>
#include "Thread.h"
Thread::Thread():autoDelete_(false) {
std::cout <<"Thread ..." << std::endl;
}
Thread::~Thread() {
std::cout << "~Thread ..." << std::endl;
}
void Thread::Start() {
pthread_create(&mThreadId_, NULL, ThreadRoutine, this);//注意此处的this,当前对象的自身指针
}
void Thread::Join() {
pthread_join(mThreadId_, NULL);
}
//这才是线程的执行体,而不是run
void* Thread::ThreadRoutine(void *arg) {
//Run 不可以直接run函数,因为它是静态函数,所以需要转换类型
Thread* thread = static_cast<Thread*>(arg);//此处把this指针转换成基类指针,也就是把派生类指针转换成基类指针,虚函数的多态,并且具有回调功能
thread->Run();//抽象基类调用自己的抽象函数
if(thread->autoDelete_){
delete thread;
return NULL;
}
}
void Thread::SetAutoDelete(bool autoDelete) {
autoDelete_ = autoDelete;
}
Main.cpp
#include <iostream>
#include <unistd.h>
#include "Thread.h"
class TestThread:public Thread{
public:
TestThread(int count):mCount_(count)
{
std::cout << "TestThread..." << std::endl;
};
~TestThread(){
std::cout << "~TestThread..." << std::endl;
};
void Run(){
while (mCount_--){
std::cout << "this is a test ..." << std::endl;
sleep(1);
}
}
private:
int mCount_;
};
int main() {
/**@brief (1)线程对象的生命周期和线程的生命周期是不一样的;
* (2)实现线程池,线程对象能够自动销毁;所以需要用动态的方式创建对象;所以要记住,多线程创建对象,一定要采用动态的方式
* ***/
// TestThread t(5);
// t.Start();//主线程已经结束,子线程还没开始,所以需要加入等待函数,
// t.Join();
TestThread* t2 = new TestThread(5);
t2->SetAutoDelete(true);
t2->Start();
t2->Join();
for (; ; )
pause();
return 0;
}
4.1.1 pthread_create()函数
具体参考博客:https://blog.csdn.net/wushuomin/article/details/80051295
pthread_create是(Unix、Linux、Mac OS X)等创建线程的函数。它的功能是创建线程(实际上就是确定调用该线程函数的入口点),在线程创建以后,就开始运行相关的线程函数。pthread_create的返回值表示成功,返回0;表示出错,返回表示-1。
函数原型:
#include <pthread.h>
int pthread_create(
pthread_t *restrict tidp, //新创建的线程ID指向的内存单元。
const pthread_attr_t *restrict attr, //线程属性,默认为NULL
void *(*start_rtn)(void *), //新创建的线程从start_rtn函数的地址开始运行
void *restrict arg //默认为NULL。若上述函数需要参数,将参数放入结构中并将地址作为arg传入。
);
(1)传参数需要注意的问题:
避免直接在传递的参数中传递发生改变的量,否则会导致结果不可测。重新申请一块内存,存入需要传递的参数,再将这个地址作为arg传入。
(2)内存泄漏的问题
在默认情况下通过pthread_create
函数创建的线程是非分离属性的,由pthread_create函数的第二个参数决定,在非分离的情况下,当一个线程结束的时候,它所占用的系统资源并没有完全真正的释放,也没有真正终止。
只有在pthread_join
函数返回时,该线程才会释放自己的资源。或者是设置在分离属性的情况下,一个线程结束会立即释放它所占用的资源。
示例代码:
#include <stdio.h>
#include <unistd.h> //调用 sleep() 函数
#include <pthread.h> //调用 pthread_create() 函数
void *ThreadFun(void *arg)
{
if (arg == NULL) {
printf("arg is NULL\n");
}
else {
printf("%s\n", (char*)arg);
}
return NULL;
}
int main()
{
int res;
char * url = "http://c.biancheng.net";
//定义两个表示线程的变量(标识符)
pthread_t myThread1,myThread2;
//创建 myThread1 线程
res = pthread_create(&myThread1, NULL, ThreadFun, NULL);
if (res != 0) {
printf("线程创建失败");
return 0;
}
sleep(5); //令主线程等到 myThread1 线程执行完成
//创建 myThread2 线程
res = pthread_create(&myThread2, NULL, ThreadFun,(void*)url);
if (res != 0) {
printf("线程创建失败");
return 0;
}
sleep(5); // 令主线程等到 mythread2 线程执行完成
return 0;
}
4.1.2 pthread_join()函数
具体参考博客:https://blog.csdn.net/fanyun_01/article/details/107647111
函数原型:
int pthread_join(pthread_t thread, void **retval);
参数说明args:
pthread_t thread: 被连接线程的线程号
void **retval : 指向一个指向被连接线程的返回码的指针的指针
return:
线程连接的状态,0是成功,非0是失败
当调用 pthread_join() 时,当前线程会处于阻塞状态,直到被调用的线程结束后,当前线程才会重新开始执行。pthread_join() 函数返回后,被调用线程才算真正意义上的结束,它的内存空间也会被释放(如果被调用线程是非分离的)。这里有三点需要注意:
被释放的内存空间仅仅是系统空间,你必须手动清除程序分配的空间,比如 malloc() 分配的空间。
一个线程只能被一个线程所连接。
被连接的线程必须是非分离的,否则连接会出错。
所以可以看出pthread_join()有两种作用:
用于等待其他线程结束:当调用 pthread_join() 时,当前线程会处于阻塞状态,直到被调用的线程结束后,当前线程才会重新开始执行。
对线程的资源进行回收:如果一个线程是非分离的(默认情况下创建的线程都是非分离)并且没有对该线程使用。pthread_join() 的话,该线程结束后并不会释放其内存空间,这会导致该线程变成了“僵尸线程”。