线程池 | Any、Semaphore类

1.Any类

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
43
44
45
46
47
48
//Any类 表示可以接受任意数据的类型
class Any
{
public:
Any() = default;
~Any() = default;
Any(const Any&) = delete;
Any& operator=(const Any&) = delete;
Any(Any&&) = default;
Any& operator=(Any&&) = default;

//这个构造函数可以让Any类型接受任意其它的数据
template <typename T>
Any(T data):base_(std::make_unique<Derive<T>>(data)){}

//这个方法能把Any对象里面存储的data数据提取出来
template<typename T>
T cast_()
{
//基类指针转为派生类指针
Derive<T>* pd = dynamic_cast<Derive<T>*>(base_.get());
if (pd == nullptr)
{
throw "type is unmatch!";
}
return pd->data_;
}

private:
//基类类型
class Base
{
public:
virtual ~Base() = default;
};

//派生类类型
template<typename T>
class Derive : public Base
{
public:
Derive(T data) :data_(data)
{}
T data_;
};
//定义一个基类指针
std::unique_ptr<Base> base_;
};

把Any类的拷贝构造和拷贝赋值都delet是因为私有成员base_是unique_ptr,unique_ptr的拷贝构造和拷贝赋值是delete的。

为什么 Any 类可以承接所有的类型

这个 Any 类可以承接所有类型的原因主要在于它使用了模板和多态的技术。

模板的使用

1
2
template <typename T>
Any(T data):base_(std::make_unique<Derive<T>>(data)){}
  • 这个构造函数是一个模板函数,这意味着它可以接受任何类型T的数据作为参数。当你使用 Any类创建对象时,例如:
    • Any a(5); 这里 Tint,编译器会实例化一个 Any<int> 的构造函数,它会将 5 存储在 Derive<int> 类型的对象中。
    • Any b(std::string("hello")); 这里 Tstd::string,编译器会实例化一个 Any<std::string> 的构造函数,它会将 "hello" 存储在 Derive<std::string> 类型的对象中。

多态的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Base
{
public:
virtual ~Base() = default;
};

template<typename T>
class Derive : public Base
{
public:
Derive(T data) :data_(data)
{}
T data_;
};
std::unique_ptr<Base> base_;
  • Any 类包含一个私有成员 base_,它是一个 std::unique_ptr<Base> 类型的指针,Base 是一个基类,并且有一个模板派生类 Derive<T> 继承自 Base
  • 在存储数据时,使用 std::make_unique<Derive<T>>(data) 创建一个 Derive<T> 的对象,并将其存储在 base_ 中。由于 Derive<T> 是从 Base 派生的,所以可以将 Derive<T> 的指针存储在 std::unique_ptr<Base> 中,这利用了多态性。

工作流程解释

假设你创建了一个 Any 对象,如 Any a(5);

  1. 调用 Any 的模板构造函数,Tint,编译器会生成一个 Any<int> 的构造函数。
  2. 这个构造函数会调用 std::make_unique<Derive<int>>(5),创建一个 Derive<int> 类型的对象,该对象存储了 int 类型的数据。
  3. 存储的数据被存储在 Derive<int>data_ 成员中。
  4. 这个 Derive<int> 对象的指针被存储在 base_ 中,由于 Derive<int> 继承自 Base,所以可以使用 std::unique_ptr<Base> 来存储它。

如何存储不同类型的数据

  • 当你存储不同类型的数据时,Any 类会为每个类型创建一个对应的 Derive<T> 派生类实例,通过多态将这些派生类对象存储在 base_ 中。
  • 每个 Derive<T> 实例都存储了具体类型的数据,并且都可以通过 base_ 指针来管理,因为它们都继承自 Base

提取数据时的类型检查

1
2
3
4
5
6
7
8
9
10
template<typename T>
T cast_()
{
Derive<T>* pd = dynamic_cast<Derive<T>*>(base_.get());
if (pd == nullptr)
{
throw "type is unmatch!";
}
return pd->data_;
}
  • 当你调用 cast_ 方法时,使用 dynamic_cast<Derive<T>*>(base_.get()) 尝试将存储的对象转换为 Derive<T>* 类型。
  • dynamic_cast 会在运行时检查存储的对象是否真的是 Derive<T> 类型。如果是,它将返回 Derive<T> 的指针,你可以访问 data_ 成员来获取存储的数据;如果不是,dynamic_cast 将返回 nullptr,此时会抛出异常,因为存储的对象类型与你想要提取的类型不匹配。

通过这种方式,Any 类利用模板和多态性可以存储和管理不同类型的数据,但在使用 cast_ 方法时,需要确保存储的对象类型和提取时指定的类型一致,否则会引发异常。

总结

  • 模板允许 Any 类接受任何类型的数据,因为对于不同类型的数据,会生成相应的 Derive<T> 实例。
  • 多态允许将这些不同类型的 Derive<T> 实例存储在 base_ 指针中,因为它们都继承自 Base
  • cast_ 方法使用 dynamic_cast 进行运行时类型检查,以确保提取的数据类型与存储的数据类型匹配。

2.Semaphore类

通过条件变量+互斥锁来实现信号量

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
//实现一个信号量类
class Semaphore
{
public:
Semaphore(int limit = 0):resLimit_(limit){}
~Semaphore() = default;

//获取一个信号量资源
void wait()
{
std::unique_lock<std::mutex> lock(mtx_);
//等待信号量有资源,灭有资源的话,会阻塞当前线程
cond_.wait(lock, [&]()->bool {return resLimit_ > 0; });
resLimit_--;
}

//增加一个信号量资源
void post()
{
std::unique_lock<std::mutex> lock(mtx_);
resLimit_++;
cond_.notify_all();
}
private:
int resLimit_;
std::mutex mtx_;//互斥锁
std::condition_variable cond_;//条件变量
};

互斥锁是只有0和1的资源计数器,一个线程拿了以后,资源计数器-1,变为0,那么其他线程不可以拿了就

信号量就是资源数量可以自定义的资源计数器,我们传给limit是多少,那就是多少

wait方法

1
2
3
4
5
6
7
8
//获取一个信号量资源
void wait()
{
std::unique_lock<std::mutex> lock(mtx_);
//等待信号量有资源,灭有资源的话,会阻塞当前线程
cond_.wait(lock, [&]()->bool {return resLimit_ > 0; });
resLimit_--;
}

获取互斥锁,然后条件变量阻塞等待资源计数器大于0,只有1.被唤醒后,2.拿到锁并且3.满足条件之后才会继续执行。一旦继续执行,那就是代表当前线程满足了条件,就是代表拿到了资源,我们的资源就得要–

post方法

1
2
3
4
5
6
7
//增加一个信号量资源
void post()
{
std::unique_lock<std::mutex> lock(mtx_);
resLimit_++;
cond_.notify_all();
}

获取互斥锁,post说明我们用完资源了,资源计数器++。我们不知道自己拿着的资源是不是最后一个,所以只要放回去资源,就要通知其他的线程有资源放回去了,让他们来使用。

想要了解更多信号量的读者:实现线程同步的方法-CSDN博客