c++11实现线程池

c++11实现线程池

c++线程库

thread创建线程和同步的方式join,detach

#include <iostream>
#include <thread>
void printf_hw(std::string s)
{
std::cout<<s<<"\n";
}
int main()
{
std::thread a(printf_hw, "nihao");
//a.join();//同步
a.detach();//异步
return 0;
}
多线程环境中的条件竞争

当多个线程同时访问一个资源时候往往会存在条件竞争问题,只进行读操作往往是无危险的,但一旦有线程进行写操作时候就会引起恶性条件竞争

  • 互斥锁mutex

互斥量是最基本的解决条件竞争的方法,在使用时候一般搭配lock_gruad或者unique_lock,这符合c++的RAII(资源获取即初始化)手法来管理内存

#include <iostream>
#include <thread>
#include <mutex>
std::mutex g_mtx; // 全局互斥锁
void f1(int &a)
{
std::lock_guard<std::mutex> lock(g_mtx); // 使用互斥锁保护临界区
++a; // 对共享资源执行自增操作
}
int main()
{
int a = 1;
std::thread t1(f1, std::ref(a)); // 使用 std::ref 创建引用包装器
std::thread t2(f1, std::ref(a)); // 由于thread并不知道你传递的是引用还是右值,需要使用std::ref显示指出
t1.join(); // 等待线程 t1 完成
t2.join(); // 等待线程 t2 完成
std::cout << "The value of a is now: " << a << std::endl; // 输出应为 3
return 0;
}
  • 死锁

死锁产生的四个必要条件是:

  1. 互斥条件:一个资源每次只能被一个进程使用。也就是说,如果其他进程请求该资源,请求者只能等待,直到该资源被当前占有者释放。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。即进程至少已经持有一个资源,但又请求其他进程持有的资源,而该资源不能被立即分配,此时请求资源的进程阻塞,但又对自己已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。即进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,只能由获得该资源的进程自己释放。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。即存在一种进程资源的循环等待链,链中的每一个进程已获得的资源同时被链中下一个进程所请求。

解决死锁有著名的银行家算法
银行家算法是一种避免死锁的著名算法,用于确保系统始终处于安全状态,从而避免死锁的发生。该算法模拟了银行家对客户的贷款过程,确保在分配资源时系统总是处于安全状态。以下是一个简单的银行家算法实现,使用C++编写:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class BankersAlgorithm {
private:
int n; // 进程数
int m; // 资源类型数
vector<int> available; // 可用资源向量
vector<vector<int>> maxClaim; // 最大需求矩阵
vector<vector<int>> allocation; // 已分配资源矩阵
vector<int> need; // 需求矩阵
public:
BankersAlgorithm(int numProcesses, int numResources, const vector<int>& availableResources,
const vector<vector<int>>& maxDemands)
: n(numProcesses), m(numResources), available(availableResources), maxClaim(maxDemands), allocation(n, vector<int>(m, 0)) {
need.resize(n, vector<int>(m, 0));
calculateNeed();
}
// 计算需求矩阵
void calculateNeed() {
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
need[i][j] = maxClaim[i][j] - allocation[i][j];
}
}
}
// 判断系统是否处于安全状态
bool isSafe() {
vector<bool> finish(n, false); // 记录进程是否完成
vector<int> work = available; // 工作向量,初始化为可用资源
vector<int> safeSeq; // 安全序列
while (true) {
bool found = false;
for (int p = 0; p < n; ++p) {
if (!finish[p] && all_of(work.begin(), work.end(), [&](int resource) {
return resource >= need[p][0];
})) {
// 如果当前进程能够完成,则将其标记为完成,并释放其资源
for (int i = 0; i < m; ++i) {
work[i] += allocation[p][i];
}
safeSeq.push_back(p);
finish[p] = true;
found = true;
break;
}
}
if (!found) {
// 如果没有找到能够完成的进程,则系统不安全
return false;
}
}
// 如果所有进程都标记为完成,则系统安全
return true;
}
// 请求资源
bool requestResources(int processId, const vector<int>& request) {
// 检查请求是否超过最大需求
for (int i = 0; i < m; ++i) {
if (request[i] > maxClaim[processId][i]) {
return false;
}
}
// 检查请求是否超过当前需求
for (int i = 0; i < m; ++i) {
if (request[i] > need[processId][i]) {
return false;
}
}
// 尝试分配资源
bool canAllocate = true;
for (int i = 0; i < m; ++i) {
if (request[i] > available[i]) {
canAllocate = false;
break;
}
}
if (!canAllocate) {
return false;
}
// 更新已分配资源
for (int i = 0; i < m; ++i) {
allocation[processId][i] += request[i];
available[i] -= request[i];
}
// 更新需求
for (int i = 0; i < m; ++i) {
need[processId][i] -= request[i];
}
// 检查系统是否仍然安全
return isSafe();
}
};
int main() {
int numProcesses = 5; // 进程数
int numResources = 3; // 资源类型数
vector<int> availableResources = {3, 3, 2}; // 可用资源向量
vector<vector<int>> maxDemands = {
{7, 5, 3},
{3, 2, 2},
{9, 0, 2},
{2, 2, 2},
{4, 3, 3}
}; // 最大需求矩阵
BankersAlgorithm ba(numProcesses, numResources, availableResources, maxDemands);
cout << "Initial state: ";
if (ba.isSafe()) {
cout << "Safe" << endl;
} else {
cout << "Not safe" << endl;
}
// 尝试请求资源
vector<int> request = {1, 0, 2};
int processId = 0; // 假设进程0请求资源
if (ba.requestResources(processId, request)) {
cout << "Process " << processId << " requested resources successfully." << endl;
} else {
cout << "Process " << processId << " failed to request resources." << endl;
}
// 再次检查系统状态
cout << "After request: ";
if (ba.isSafe()) {
cout << "Safe" << endl;
} else {
cout << "Not safe" << endl;
}
return 0;
}

上面的代码定义了一个BankersAlgorithm类,该类包含几个方法:

  • calculateNeed():计算需求矩阵。
  • isSafe():判断系统是否处于安全状态。
  • requestResources():处理进程的资源请求。

main函数中,我们创建了BankersAlgorithm的一个实例,并初始化了进程数、资源类型数、可用资源向量和最大需求矩阵。然后,我们检查了系统的初始状态是否为安全状态,并尝试让进程0请求资源。最后,我们再次检查系统状态。

此外保持对资源访问的顺序一致也是避免死锁的方法
通过对资源访问的顺序进行控制来避免死锁是一种常用的策略,也称为资源分级法或资源有序分配法。这种策略要求每个进程在请求资源时,必须按照一个固定的顺序来请求资源。如果所有进程都按照这个顺序来请求资源,那么死锁就不会发生,因为循环等待条件无法满足。

以下是一个简单的C++实现,展示了如何通过对资源访问的顺序控制来避免死锁:

#include <iostream>
#include <vector>
#include <string>
#include <stdexcept>
using namespace std;
class Resource {
private:
int available;
int maxCount;
string resourceName;
public:
Resource(int count, string name) : available(count), maxCount(count), resourceName(name) {}
bool request(int amount) {
if (amount <= 0 || amount > available) {
return false;
}
available -= amount;
return true;
}
bool release(int amount) {
if (amount <= 0 || amount > maxCount - available) {
return false;
}
available += amount;
return true;
}
int getAvailable() const {
return available;
}
string getName() const {
return resourceName;
}
};
class ResourceManager {
private:
vector<Resource> resources;
vector<string> resourceOrder; // 资源请求顺序
public:
ResourceManager(const vector<pair<int, string>>& initResources, const vector<string>& order) {
for (const auto& resource : initResources) {
resources.emplace_back(resource.first, resource.second);
}
resourceOrder = order;
}
bool requestResources(const vector<pair<string, int>>& requests) {
// 按照资源请求顺序检查资源是否可用
for (const auto& request : requests) {
auto it = find(resourceOrder.begin(), resourceOrder.end(), request.first);
if (it == resourceOrder.end()) {
throw runtime_error("Unknown resource in request");
}
size_t index = distance(resourceOrder.begin(), it);
Resource& res = resources[index];
if (!res.request(request.second)) {
// 如果资源不可用,则尝试释放之前已经分配的资源
for (size_t i = 0; i < index; ++i) {
resources[i].release(resources[i].maxCount);
}
return false;
}
}
return true;
}
void releaseResources(const vector<pair<string, int>>& releases) {
for (const auto& release : releases) {
auto it = find(resourceOrder.begin(), resourceOrder.end(), release.first);
if (it == resourceOrder.end()) {
throw runtime_error("Unknown resource in release");
}
size_t index = distance(resourceOrder.begin(), it);
Resource& res = resources[index];
if (!res.release(release.second)) {
throw runtime_error("Failed to release resource");
}
}
}
};
int main() {
// 初始化资源
vector<pair<int, string>> initResources = {{3, "R1"}, {2, "R2"}};
// 资源请求顺序
vector<string> resourceOrder = {"R1", "R2"};
ResourceManager rm(initResources, resourceOrder);
// 进程A请求资源
vector<pair<string, int>> requestsA = {{"R1", 1}, {"R2", 1}};
if (rm.requestResources(requestsA)) {
cout << "Process A acquired resources successfully." << endl;
} else {
cout << "Process A failed to acquire resources." << endl;
}
// 进程B请求资源,注意按照顺序请求
vector<pair<string, int>> requestsB = {{"R1", 1}, {"R2", 1}};
if (rm.requestResources(requestsB)) {
cout << "Process B acquired resources successfully." << endl;
} else {
cout << "Process B failed to acquire resources." << endl;
}
// 释放资源
rm.releaseResources(requestsA);
rm.releaseResources(requestsB);
// 如果还有其他的进程请求资源,它们也应该按照资源请求顺序来请求资源
// 示例中不再添加其他进程请求资源的代码,但你应该遵循相同的模式
cout << "All resources have been released." << endl;
return 0;
}

在上面的代码中,ResourceManager 类负责管理资源,并且要求所有资源请求都按照一个预定义的顺序进行。requestResources 方法会根据这个顺序来检查资源是否可用,并尝试分配资源。如果某个资源不可用,则会释放之前已经分配的资源,并返回 false 表示请求失败。releaseResources 方法用于释放资源。

每个进程在请求资源时,必须遵循 resourceOrder 中定义的顺序。如果进程尝试以不同的顺序请求资源,则应该抛出异常或进行错误处理。

请注意,虽然上述方法能够避免死锁,但它也可能导致饥饿问题,即某些进程可能永远得不到所需的资源,因为其他进程总是先得到它们。因此,在设计资源管理系统时,还需要考虑公平性和饥饿问题。

此外,上述示例是一个简化模型,实际应用中可能涉及到更复杂的资源类型和请求模式,例如嵌套锁、可重入锁、读写锁等。在设计并发系统时,还需要考虑性能、易用性和可维护性等因素。

PV操作

生产者消费者模型也是很经典了,这是我结合c++11多线程的实现
用到了新的知识点std::condition_variable

#include <mutex>
#include <iostream>
#include <condition_variable>
#include <thread>
#include <string>
#include <queue>
std::queue<int> g_qe;
std::mutex g_mtx;
std::condition_variable g_cv;
void Producer()
{
for(int i = 0; i < 10; ++i)
{
{
std::unique_lock<std::mutex> lock(g_mtx);
g_qe.push(i);
std::cout<<"task :"<<i<<std::endl;
g_cv.notify_one();
}
std::this_thread::sleep_for(std::chrono::microseconds(100));
}
}
void Consumer()
{
while(true)
{
std::unique_lock<std::mutex> lock(g_mtx);
g_cv.wait(lock, []()->bool{
return !g_qe.empty();
});
int tempValue = g_qe.front();
g_qe.pop();
std::cout<<"use :"<<tempValue<<std::endl;
}
}
int main()
{
std::thread t1(Producer);
std::thread t2(Consumer);
t1.join();
t2.join();
return 0;
}
线程池实现

线程池的思想主要源于对系统资源的高效利用和避免频繁创建与销毁线程的开销。当系统中存在大量并发线程,且每个线程执行的任务时间较短时,频繁地创建和销毁线程会大大降低系统的效率。线程池通过在系统中开辟一块区域,存放一些待命的线程,当有任务需要执行时,从线程池中取出一个线程来执行,任务完成后线程归还回线程池,从而避免了大量创建和销毁线程的开销。而生产线程与任务队列之间就是典型的生产者消费者模型

#include <iostream>
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <functional>
#include <string>
class ThreadPool {
public:
ThreadPool(int);
template<class F, class... Args>
void enqueue(F &&f, Args&&... args);
~ThreadPool();
private:
// 需要保持对线程的跟踪,以便我们可以加入它们
std::vector<std::thread> threads;
// 任务队列
std::queue<std::function<void()>> tasks;
// 同步
std::mutex queue_mutex;
std::condition_variable condition;
bool stop;
};
// 构造函数只是启动一些数量的工作线程
ThreadPool::ThreadPool(int numThreads):stop(false)
{
for(int i = 0; i < numThreads; ++i)
{
threads.emplace_back([this]()->void{
while(true)
{
std::unique_lock<std::mutex> lock(queue_mutex);
condition.wait(lock, [this]()->bool{
return !tasks.empty() || stop;
});
if(stop && tasks.empty())
{
return;
}
std::function<void()> task(std::move(tasks.front()));
tasks.pop();
lock.unlock();
task();
}
});
}
}
// 添加新工作项到线程池
template<class F, class... Args>
void ThreadPool::enqueue(F &&f, Args&&... args)
{
std::function<void()> task = std::bind(std::forward<F>(f),std::forward<Args>(args)...);
{
std::unique_lock<std::mutex> lock(queue_mutex);
tasks.emplace(std::move(task));
}
condition.notify_one();
}
// 析构函数会等待所有线程结束
ThreadPool::~ThreadPool()
{
{
std::unique_lock<std::mutex> lock(queue_mutex);
stop = true;
}
condition.notify_all();
for(std::thread &worker: threads)
{
worker.join();
}
}
int main()
{
ThreadPool pool(4);
for (int i = 0; i < 15; ++i)
{
pool.enqueue([i] ()->void{
std::cout << "Task " << i << " is running in thread " << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Task " << i << " is done" << std::endl;
});
}
return 0;
}
future
原子操作
posted @   李小飞11  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示