4-4

设计模式之单例模式

设计模式

设计模式简单来说就是在解决某一类问题场景时,有既定的,优秀的代码框架可以直接使用,代码更易于维护,代码的可读性,复用性,可移植性,健壮性会更好

单例模式

概念

单例模式是指在内存中只会创建一个对象,且只能创建一个对象的设计模式,在程序中多次调用同一个对象且作用相同时,为了防止多次创建对象耗费大量内存,单例模式让程序在内存只创建一个对象,所有调用的地方共享该对象。

类型

懒汉式

在真正使用该对象时才去创建该对象,因为很懒~
懒汉式创建对象的方法是在程序使用对象前,先判断该对象是否已经实例化(判空),若已实例化直接返回该类对象,否则则先执行实例化操作。
alt text

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Singleton 
{
public:
static Singleton* getInstance()
{
if(single == nullptr)
{
single = new Singleton();
}
return single;
}
private:
Singleton(){cout << "create" << endl;};
~Singleton()
{

cout << "delete" <<endl;
//delete single;

}
static Singleton* single;
// 定义一个嵌套类,在该类的析构函数中,自动释放外层类的资源
class CRelease
{
public:
~CRelease() { delete single; }
};
// 通过该静态对象在程序结束时自动析构的特点,来释放外层类的对象资源
static CRelease release;

};
Singleton* Singleton::single = nullptr;
Singleton::CRelease Singleton::release;

构造函数私有化,这样用户就不能任意定义该类型的对象了
定义该类型唯一的对象
通过一个static静态成员方法返回唯一的对象实例

由于析构函数被声明为私有的,无法通过delete 来释放掉内存, 为防止内存泄漏(虽然当前进程结束的时候,系统反正会回收分配给它的所有资源,包括未回收的内存),在类内嵌套了一个类,利用static静态对象在程序结束时自动析构这么一个特征。

懒汉式如何保证只创建一个对象

若多个线程同时判断 singleton == nullptr, 则会同时创建一个对象,如何保证安全?
通过加互斥锁 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Singleton 
{
public:
static Singleton* getInstance()
{
if(single == nullptr)
{
//获取锁
if(single == nullptr) //锁加双重判断,第一个if是保证了只有第一次创建对象时加锁解锁,保证了效率
//第二个if是保证了多个线程进入后不会建立多个对象
{

single = new Singleton();
}
//释放锁
}
return single;
}
private:
Singleton(){cout << "create" << endl;};
~Singleton()
{

cout << "delete" <<endl;
//delete single;

}
static Singleton* single;
// 定义一个嵌套类,在该类的析构函数中,自动释放外层类的资源
class CRelease
{
public:
~CRelease() { delete single; }
};
// 通过该静态对象在程序结束时自动析构的特点,来释放外层类的对象资源
static CRelease release;
static mutex mtx; //要声明为静态,静态成员函数只能访问静态数据成员和静态成员函数

};
Singleton* Singleton::single = nullptr;
Singleton::CRelease Singleton::release;

另一种保证线程安全且能在main结束后自动释放对象的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
using namespace std;

class CSingleton
{
public:
static CSingleton* getInstance()
{
static CSingleton single; // 懒汉式单例模式,定义唯一的对象实例,已经将拷贝构造和析构声明为private了,在类外无法拷贝赋值和调用析构,所以返回的是single的地址,而不是single本身
return &single;
}
private:
CSingleton() { cout << "CSingleton()" << endl; }
~CSingleton() { cout << "~CSingleton()" << endl;}
CSingleton(const CSingleton&);
};
int main()
{
CSingleton *p1 = CSingleton::getInstance();
CSingleton *p2 = CSingleton::getInstance();
CSingleton *p3 = CSingleton::getInstance();
return 0;
}

对于static静态局部变量的初始化,编译器会自动对它的初始化进行加锁和解锁控制,使静态局部变量的初始化成为线程安全的操作,不用担心多个线程都会初始化静态局部变量,因此上面的懒汉单例模式是线程安全的单例模式

静态局部变量的生命周期贯穿整个程序的运行时间,但是它们的初始化是延迟的,也就是说,它们只在首次被引用时初始化。这是C++标准中静态局部变量的一个重要特性,称为“Magic Statics”(也称作“函数内的静态局部变量”或“局部静态变量”)。

当你在函数内部声明一个静态局部变量时,这个变量的存储空间在程序开始运行时就已经被分配了,但是它的初始化会被推迟到该函数首次被调用,且到达该变量声明的时候。一旦初始化完成,该变量的值就会在程序的后续调用中保持不变,直到程序结束。

静态变量*******

饿汉式

在类加载时已经创建好了该单例对象,等待程序使用
在编码时就已经指明了要马上创建这个对象,不需要等到被调用时再去创建。
alt text

1
2
3
4
5
6
7
8
9
10
11
12
13
class Singleton
{
public:
static Singleton* getInstance()
{
return &single;
}
private:
Singleton();
~Singleton();
static Singleton single;
}
Singleton Singleton::single;

线程池项目

第一个C++项目:线程池

出现的一些问题:

1.线程池启动后报错,主函数代码如图

alt text
原因:此时注释掉了sleep函数,线程池中的线程在尝试访问某些资源时,主函数 main 已经结束并开始了进程的终止阶段。在这个过程中,所有线程都会被强制终止,而线程池中的线程可能还在执行中,这导致了资源访问冲突。
当主函数结束时,线程池被析构掉了,里面的资源可能已经被释放或变得不可用,导致线程访问无效内存。
加上sleep延长了主线程的执行时间,创建的其他线程有足够的时间去访问线程池中的资源

3-30

类型转换

const_cast<>

去掉(指针或引用)常量属性,里面必须时指针或引用类型,但通过指针也无法更改常量的值
alt text

static_cast<>

提供编译器认为安全的类型转换
转换有效:alt text
转换无效:alt text

reinterpret_cast<>

类似c风格的强制类型转换, 不安全
alt text转换有效

dynamic_cast<>

用于继承结构中,可以支持RTTI类型识别的上下转换
alt text
alt text

C++多线程编程

相关库:

#include <thread>
#include <mutex> //互斥锁
#include <condition_variable>

相关函数实现

线程:

创建线程: thread t(func, a, b, ...); // 创建一个线程对象, func为线程函数, a, b...为函数参数
t.join() //主线程要等待子线程t运行结束
若主线程运行结束后,还留有未运行完成的子线程会报错
可以用: t.detch() //把子线程t设置为和主线程分离的线程, 这样就不会报错
std::this_thread命名空间, 里面封装了许多函数
如:get_id() //获取线程id
    sleep() // 睡眠
    sleep_for() //睡多长时间
    sleep_until() //睡到什么时候
std::chrono命名空间, 封装了一些时间相关的函数

锁:

std::mutex mtx;//定义一把锁
mtx.lock() , mtx.unlock() //加锁解锁
但由于加锁后若直接return了可能unlock调用不到无法解锁, 造成死锁

解决:
std::mutex mtx;
std::lock_guard<std::mutex> lck(mtx);
使用lock_guard封装了一把锁,出了{}(作用域)后会自动析构,无需担心死锁问题,也不用手动加锁解锁,禁止了拷贝构造和拷贝复制,**所以不能用于函数参数或者返回值**

std::mutex mtx;
std::unique_lock<std::mutex> lck(mtx);
使用unique_lock封装了一把锁,出了{}(作用域)后会自动析构,无需担心死锁问题,也不用手动加锁解锁
禁止了拷贝构造和拷贝复制,**可以使用移动构造和移动复制**,类似于unique_ptr
二者都运用了智能指针的思想

线程通信:

条件变量:condition_variable

常与一把互斥锁一起使用
定义: condition_variable cv;
cv.wait(lck); // lck 只能是unique_lock<std::mutex>类型
作用:首先线程的状态变为等待状态, 第二步释放所拿到的锁
cv.notify_all() / cv.notify_one()
作用:唤醒其他的所有/一个线程,被唤醒的线程由等待变为*阻塞*状态,只有拿到锁后才变成就绪态

信号量:

表示资源数量?
P操作:资源数+1
V操作:资源数-1
二元信号量时作用跟互斥锁是类似的,但信号量所有线程都可以操作,但互斥锁只能由持有它的线程释放这把锁

实验报告

山东大学计算机科学与技术学院 云计算技术 课程实验报告

学号:

202100130121

姓名:

肖智远

班级:

21数据

实验题目:

利用主流云平台搭建个人博客或网站

实验学时:2

实验日期:2024.3.22

实验目的:

熟悉使用主流云平台并搭建个人博客或者网站。

具体包括:

参考方案:基于主流云平台,设计、实现个人博客或者网站的搭建,撰写实验报告(附带网站链接并可以访问),并在网站上呈现此次实验报告。

硬件环境:

联网的计算机一台

软件环境:

Windows or Linux

实验步骤与内容:

个人博客网站地址:xzyyyyy.love

一、确定需求和目标

在开始搭建个人博客或网站之前,首先需要明确你的需求和目标。例如,你可能希望博客具备发布文章、评论互动、分类归档等功能;或者你可能希望网站能够展示你的作品、提供联系方式等。明确需求和目标有助于你在后续的设计和实现过程中保持清晰的方向。

二、选择开发工具和平台

根据你的需求和目标,选择合适的开发工具和平台。常见的网站开发工具有HTML、CSS、JavaScript等前端技术,以及PHP、Python、Java等后端技术。同时,还需要选择一个适合你的网站托管平台,如GitHub Pages、WordPress、阿里云等。

三、设计网站结构和界面

根据需求和目标,设计网站的整体结构和界面。这包括确定网站的导航栏、页面布局、颜色搭配等。你可以使用草图工具或设计软件进行设计,并导出相应的设计图。

四、搭建网站框架

使用选择的开发工具和平台,开始搭建网站的框架。这包括创建基本的HTML页面结构,添加CSS样式以及编写JavaScript脚本实现交互效果。对于后端部分,你可能需要设置数据库、编写服务器端的代码等。

结论分析与体会:

学会了搭建个人博客的过程以及放到github的过程