假设我在生产者 - 消费者设置中有两个进程(在此示例中模拟了两个线程)。也就是说,一个进程将数据写入文件,另一个进程使用文件中的数据,然后清除所述文件。
我目前拥有的设置,基于我从各种资源在线投入的点点滴滴,是我应该使用锁定文件来确保一次只有一个进程可以访问数据文件。生产者获取锁,写入文件,然后释放锁。同时,消费者使用inotify等待修改事件,此时它获取锁,消耗数据并清空文件。
这似乎相对简单,但是让我失望的部分是当我在我的消费者线程中清空文件时,它再次触发inotify修改事件,再次引发整个流程,并以数据文件结束再次清除,因此永远重复。
我已经尝试了几种方法来解决这个问题,但它们似乎都不对。我担心做错了会引入潜在的竞争条件,或者我最终会跳过修改事件等等。
这是我目前的代码:
#include <fstream>
#include <iostream>
#include <string>
#include "pthread.h"
#include "sys/file.h"
#include "sys/inotify.h"
#include "sys/stat.h"
#include "unistd.h"
const char* lock_filename = "./test_lock_file";
const char* data_filename = "./test_data_file";
int AquireLock(char const* lockName) {
mode_t m = umask(0);
int fd = open(lockName, O_RDWR | O_CREAT, 0666);
umask(m);
bool success = false;
if (fd < 0 || flock(fd, LOCK_EX) < 0) {
close(fd);
return -1;
}
return fd;
}
void ReleaseLock(int fd, char const* lockName) {
if (fd < 0) return;
remove(lockName);
close(fd);
}
void* ConsumerThread(void*) {
// Set up inotify.
int file_descriptor = inotify_init();
if (file_descriptor < 0) return nullptr;
int watch_descriptor =
inotify_add_watch(file_descriptor, data_filename, IN_MODIFY);
if (watch_descriptor < 0) return nullptr;
char buf[4096] __attribute__((aligned(__alignof__(inotify_event))));
while (true) {
// Read new events.
const inotify_event* event;
ssize_t numRead = read(file_descriptor, buf, sizeof(buf));
if (numRead <= 0) return nullptr;
// For each event, do stuff.
for (int i = 0; i < numRead; i += sizeof(inotify_event) + event->len) {
event = reinterpret_cast<inotify_event*>(&buf[i]);
// Critical section!
int fd = AquireLock(lock_filename);
// Read from the file.
std::string line;
std::ifstream data_file(data_filename);
if (data_file.is_open()) {
while (getline(data_file, line)) {
std::cout << line << std::endl;
}
data_file.close();
// Clear the file by opening then closing without writing to it.
std::ofstream erase_data_file(data_filename);
erase_data_file.close();
std::cout << "file cleared." << std::endl;
}
ReleaseLock(fd, lock_filename);
// Critical section over!
}
}
return nullptr;
}
int main(int argv, char** argc) {
// Set up other thread.
pthread_t thread;
int rc = pthread_create(&thread, NULL, ConsumerThread, nullptr);
if (rc) return rc;
// Producer thread: Periodically write to a file.
while (true) {
sleep(3);
// Critical section!
int fd = AquireLock(lock_filename);
// Write some text to a file
std::ofstream data_file(data_filename);
int counter = 0;
if (data_file.is_open()) {
std::cout << "Writing to file.\n";
data_file << "This is some example data. " << counter++ << "\n";
data_file.close();
}
ReleaseLock(fd, lock_filename);
// Critical section over!
}
pthread_exit(NULL);
return 0;
}
我的一个想法是在使用inotify_rm_watch
的消费者线程的关键部分的开头禁用跟踪修改事件,然后在离开临界区之前重新添加它。这似乎不起作用。即使事件被禁用,修改事件仍然会被触发,我不知道为什么。
我还考虑过使用布尔值来查看在使用文件时是否有任何文件内容,并且仅在文件不为空时清除文件。这感觉有点hacky,因为它仍然在循环中进行第二次不必要的迭代,但如果我找不到更好的解决方案,我可能会选择它。理想情况下,只有生产者线程的修改才能触发事件,而消费者可能会以某种方式忽略或禁用它自己的文件修改,但我不确定如何实现这种效果。