【问题标题】:Effort to write a thread-safe C++ wrapper of C API努力编写 C API 的线程安全 C++ 包装器
【发布时间】:2023-03-25 01:09:01
【问题描述】:

我正在编写 C API 的“线程安全”C++ 包装器,而 API 本身不是内部线程安全的。我尝试过使用 RAII。

我想知道,我的实现是否正确?以及它是否是线程安全的。我感谢我的代码中的任何 cmets。提前致谢!

要包装的C API如下,

 /* an data structure which represents a connection proxy to the logger: */
struct cLog_Logger;

/* connect the logger, and returns a handle to it: */
cLog_Logger* cLog_connect();

/* appends a zero terminated string to the log: */
void cLog_write(cLog_Logger* logger, const char* message);

/* closes the connection with the logger: */
void cLog_close(cLog_Logger* logger);

我的包装器实现如下:

    class LoggerWrapper{

    public:
        LoggerWrapper(){              //constructor
            cLog= cLog_connect();
        }

        void log(const std::string &message){    //entry point
            cLog_write(cLog, message);
            cLog_close(cLog);
            }

        ~LoggerWrapper(){        //destructor
            delete cLog;
        }
    protected:
         cLog_Logger *cLog;
}

谢谢!

【问题讨论】:

  • 线程在哪里?
  • @Ajay,线程在包装器之外无处不在:)

标签: c++ thread-safety wrapper


【解决方案1】:

我认为您需要像这样更改实现:

class LoggerWrapper{

public:
    LoggerWrapper(){              //constructor
        cLog= cLog_connect();
    }

    void log(const std::string &message){    //entry point
        cLog_write(cLog, message);
        }

    ~LoggerWrapper(){        //destructor
        cLog_close(cLog);
        delete cLog;
    }
protected:
     cLog_Logger *cLog;
} ;

这使您可以编写如下代码:

LoggerWrapper logger ;
logger.log("Something") ;
logger.log("Something else) ;

所以用同一个对象制作多个日志;否则第一次调用会关闭记录器并且对象是无用的。这是你想要的吗?

然后是第二个问题:线程安全是什么意思?您想从不同线程对同一个对象进行日志记录吗?

然后你可以像这样在日志函数中添加一个互斥锁和一个锁守卫:

class LoggerWrapper{

public:
    LoggerWrapper(){              //constructor
        cLog= cLog_connect();
    }

    void log(const std::string &message){    //entry point
        std::lock_guard<std::mutex> guard(mutex);
        cLog_write(cLog, message);
        }

    ~LoggerWrapper(){        //destructor
        cLog_close(cLog);
        delete cLog;
    }
protected:
     cLog_Logger *cLog;
     std::mutex mutex ;
} ;

【讨论】:

  • 我认为互斥锁作为非静态类成员不会解决问题。想象一下,在 2 个不同的线程中创建 2 个 LoggerWrapper 实例。轰隆隆!
  • @marom,将'cLog_close(cLog)' 移到析构函数是一个好点,我的考虑是在日志写入完成后立即关闭连接,虽然我不确定是否这是有道理的。
  • @marom,您对“线程安全”的猜测是正确的,API 没有避免同步问题的机制。您的解决方案似乎是正确的,非常感谢!
【解决方案2】:

简短回答:不,不是。一开始我觉得肯定是free()而不是delete,因为是c api。 您所做的可以使您的程序无资源泄漏,但不是线程安全的。 RAII 是为了避免资源泄漏。 有一种简单但低效的方法来包装您的 API 以实现线程安全,以便在您的 RAII 类中添加静态互斥锁。

#include <mutex>

class LoggerWrapper{

public:
    LoggerWrapper() : l(globalLock);
    {              //constructor
        cLog= cLog_connect();
    }

    void log(const std::string &message){    //entry point
        cLog_write(cLog, message);
        cLog_close(cLog);
        }

    ~LoggerWrapper(){        //destructor
        free(cLog); // I think here must be free(), but not sure
    }
protected:
    cLog_Logger *cLog;
    static std::mutex globalLock;
    std::lock_guard<std::mutex> l;
}

std::mutex LoggerWrapper::globalLock;

【讨论】:

  • 谢谢!当我们讨论 c++ 包装类时,我认为 ree() 和 delete 不会造成显着差异。
  • 能解释一下lock_guard成员的用法吗?成员 'l' 在初始化时分配,但以后不做任何事情。
  • @ulyssis2,lock_guard是RAII模板,在构造函数中获取互斥锁,在析构函数中释放互斥锁。当 lock_guard 存在时,它将互斥锁保持在锁定状态。
  • @ulyssis2, free() 或 delete 非常重要。如果你从 c api 获得指向内存的指针,它显然是从 malloc() 获得的。所以你必须只用free()来处理它,而不是delete,或者c api可以提供特殊的功能来释放连接的日志。即使删除所有作品,也不代表代码是对的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-15
  • 2011-06-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多