Interprocess communication - Binary data/serialized objects [closed] - c++

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 8 years ago.
Improve this question
I currently have two processes running on the same computer, a 'producer' and a Qt application tasked with displaying the data. These two need to exchange binary data (byte arrays, serialized objects). The data 'parcels' may range in size from a handful of bytes to tens of MBs.
What's a/the simple and elegant way to do this?
I thought about using boost::asio and Google Protocol Buffers, shared memory regions or just low-level sockets to achieve this, but I'm interested in learning about other solutions that I may have overlooked.
Performance is not absolutely critical but decent latency (<1 sec) and bandwidth (let's say, >5MB/sec) are necessary.
Thanks in advance!

If you don't like extra libraries then you can try the following:
#if defined _WIN32 || defined _WIN64
#include <windows.h>
#else
#include <sys/types.h>
#include <sys/mman.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <unistd.h>
#endif
#include <cstdint>
typedef struct
{
#if defined _WIN32 || defined _WIN64
void* hFileMap;
#else
int hFileMap;
#endif
void* pData;
size_t size;
} MemoryMap;
void* CreateMemoryMap(MemoryMap* info, const char* MapName, unsigned int size)
{
#if defined _WIN32 || defined _WIN64
info->hFileMap = NULL;
info->pData = NULL;
info->size = 0;
if ((info->hFileMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, size, MapName)) == NULL)
{
return NULL;
}
if ((info->pData = MapViewOfFile(info->hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, size)) == NULL)
{
CloseHandle(info->hFileMap);
return NULL;
}
#else
info->hFileMap = NULL;
info->pData = NULL;
info->size = 0;
if ((info->hFileMap = open(MapName, O_RDWR | O_CREAT, 438)) == -1)
{
return NULL;
}
if ((info->pData = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, hFileMap, 0)) == MAP_FAILED)
{
close(hFileMap);
return NULL;
}
#endif
info->size = size;
return info->pData;
}
void* OpenMemoryMap(MemoryMap* info, const char* MapName, unsigned int size)
{
#if defined _WIN32 || defined _WIN64
info->hFileMap = NULL;
info->pData = NULL;
info->size = 0;
if ((info->hFileMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, false, MapName)) == NULL)
{
return NULL;
}
if ((info->pData = MapViewOfFile(info->hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, size)) == NULL)
{
CloseHandle(info->hFileMap);
return NULL;
}
#else
info->hFileMap = NULL;
info->pData = NULL;
info->size = 0;
if ((info->hFileMap = open(MapName, O_RDWR | O_CREAT, 438)) == -1)
{
return NULL;
}
if ((info->pData = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, hFileMap, 0)) == MAP_FAILED)
{
close(info->hFileMap);
return NULL;
}
#endif
info->size = size;
return info->pData;
}
void CloseMap(MemoryMap* data)
{
#if defined _WIN32 || defined _WIN64
UnmapViewOfFile(data->pData);
CloseHandle(data->hFileMap);
#else
munmap(data->pData, data->size);
close(data->hFileMap);
#endif
}
template<typename T>
class CAllocator
{
private:
size_t size;
void* data = nullptr;
public:
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef T value_type;
CAllocator() {}
CAllocator(void* data_ptr, size_type max_size) noexcept : size(max_size), data(data_ptr) {};
template<typename U>
CAllocator(const CAllocator<U>& other) noexcept {};
CAllocator(const CAllocator &other) : size(other.size), data(other.data) {}
template<typename U>
struct rebind {typedef CAllocator<U> other;};
pointer allocate(size_type n, const void* hint = 0) {return static_cast<pointer>(data);}
void deallocate(void* ptr, size_type n) {}
size_type max_size() const {return size / sizeof(T);}
};
template <typename T, typename U>
inline bool operator == (const CAllocator<T>&, const CAllocator<U>&) {return true;}
template <typename T, typename U>
inline bool operator != (const CAllocator<T>& a, const CAllocator<U>& b) {return !(a == b);}
/** Test case **/
#include <vector>
#include <iostream>
#include <stdexcept>
int main()
{
/** Sender **/
MemoryMap data = {0};
if (!CreateMemoryMap(&data, "MapName", 1024))
{
if (!OpenMemoryMap(&data, "MapName", 1024))
{
throw std::runtime_error("Cannot map memory.");
}
}
std::vector<int, CAllocator<int>> shared_sender_vector(CAllocator<int>(data.pData, data.size));
shared_sender_vector.push_back(10);
for (int i = 0; i < 10; ++i)
{
shared_sender_vector.push_back(i + 1);
}
/** Receiver **/
MemoryMap data2 = {0};
if (!CreateMemoryMap(&data2, "MapName", 1024))
{
if (!OpenMemoryMap(&data2, "MapName", 1024))
{
throw std::runtime_error("Cannot map memory.");
}
}
int* offset = static_cast<int*>(data2.pData);
std::vector<int, CAllocator<int>> shared_receiver_vector(CAllocator<int>(++offset, data2.size));
shared_receiver_vector.reserve(*(--offset));
for (int i = 0; i < 10; ++i)
{
std::cout<<shared_receiver_vector[i]<<" ";
}
CloseMap(&data);
CloseMap(&data2);
}
It prints:
1 2 3 4 5 6 7 8 9 10
Can be used for strings and all sorts of containers that accepts allocators. They store their data directly in the shared-memory map. The allocator is the same as the one I wrote for:
Boost Pool experience requested. Is it useful as allocator with preallocation? so it can be reused for whatever you want..

Related

Fail to Read Through Shared Memory

I am trying to publish some random things over shared memory; and for some weird reason, the reader doesn't pick up what the sender has written
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <sys/types.h>
#include <cstdio>
class SHM {
volatile char* _ptr;
public:
SHM() {
const auto handle = shm_open("myTest", O_RDWR|O_CREAT, 0666);
const auto size = 4 * 1024 * 1024;
if (-1 == ftruncate(handle, size)) {
throw;
}
_ptr = (volatile char*)mmap(0,size , PROT_READ | PROT_WRITE, MAP_SHARED, handle, 0);
if(_ptr == MAP_FAILED){
throw;
}
int rc = fchmod(handle, 0666);
if (rc == -1) {
throw;
}
}
bool read(uint64_t& magic, uint64_t& time) {
const uint64_t newVal = *(uint64_t*)_ptr;
if (newVal != magic) {
magic = newVal;
printf("value changed!!!\n");
time = *(uint64_t*)(_ptr + sizeof(magic));
return true;
}
//printf("old value: %lu\n", newVal);
return false;
}
void publish(const uint64_t time) {
__sync_fetch_and_add((uint64_t*)_ptr, time);
__sync_synchronize();
*(uint64_t*)(_ptr + sizeof(uint64_t)) = time;
}
};
Here is the sender:
#include <ctime>
#include <unistd.h>
#include <cstdlib>
#include <cstdint>
#include "shm.h"
int main() {
SHM shm;
timespec t;
for (auto i = 0; i < 10000; i++) {
if (0 == clock_gettime(CLOCK_REALTIME, &t)) {
const uint64_t v = t.tv_sec * 1000 * 1000 * 1000 + t.tv_nsec;
shm.publish(v);
printf("published %lu\n", v);
usleep(100);
}
}
}
Here is the reader:
#include <iostream>
#include "shm.h"
int main() {
SHM shm;
uint64_t magic = 0;
uint64_t t = 0;
while (true) {
if (shm.read(magic, t)) {
printf("%lu, %lu\n", magic, t);
}
}
}
If I restart the reader, the reader is indeed able to read the last value that the sender has written.
However, if I start the reader first, and then the sender, all the values the sender writes aren't picked up by the reader.
To make this even weirder, if I uncomment the printf statement in SHM::read(), then the reader is able to pick up sometimes.
Any idea?
GCC version:
g++ (GCC) 7.2.1 20170829 (Red Hat 7.2.1-1)
I spotted a couple of issues, however, I am unsure if they would fix your problem.
name for shm_open should start with / for portable use.
In read and publish the casts must not discard volatile. E.g.: const uint64_t newVal = *(uint64_t volatile*)_ptr;. Even better, drop volatile and use std::atomic.
Although there are different processes involved, this is still the case of same objects being accessed by more than one thread of execution and at least one of these threads modifies the shared objects.
I made the above changes. Using std::atomic fixed it:
class SHM {
void* _ptr;
public:
SHM() {
const auto handle = shm_open("/myTest", O_RDWR|O_CREAT, 0666);
const auto size = 4 * 1024 * 1024;
if (-1 == ftruncate(handle, size))
throw;
_ptr = mmap(0,size , PROT_READ | PROT_WRITE, MAP_SHARED, handle, 0);
if(_ptr == MAP_FAILED)
throw;
}
bool read(uint64_t& magic, uint64_t& time) {
auto p = static_cast<std::atomic<uint64_t>*>(_ptr);
const uint64_t newVal = p[0];
if (newVal != magic) {
magic = newVal;
printf("value changed!!!\n");
time = p[1];
return true;
}
return false;
}
void publish(const uint64_t time) {
auto p = static_cast<std::atomic<uint64_t>*>(_ptr);
p[0] += time;
p[1] = time;
}
};
void sender() {
SHM shm;
timespec t;
for (auto i = 0; i < 10000; i++) {
if (0 == clock_gettime(CLOCK_REALTIME, &t)) {
const uint64_t v = t.tv_sec * 1000 * 1000 * 1000 + t.tv_nsec;
shm.publish(v);
printf("published %lu\n", v);
usleep(100);
}
}
}
void reader() {
SHM shm;
uint64_t magic = 0;
uint64_t t = 0;
while (true) {
if (shm.read(magic, t)) {
printf("%lu, %lu\n", magic, t);
}
}
}
int main(int ac, char**) {
if(ac > 1)
reader();
else
sender();
}
With std::atomic you can have more control. E.g.:
struct Data {
std::atomic<uint64_t> time;
std::atomic<uint64_t> generation;
};
// ...
bool read(uint64_t& generation, uint64_t& time) {
auto data = static_cast<Data*>(_ptr);
auto new_generation = data->generation.load(std::memory_order_acquire); // 1. Syncronizes with (2).
if(generation == new_generation)
return false;
generation = new_generation;
time = data->time.load(std::memory_order_relaxed);
printf("value changed!!!\n");
return true;
}
void publish(const uint64_t time) {
auto data = static_cast<Data*>(_ptr);
data->time.store(time, std::memory_order_relaxed);
data->generation.fetch_add(time, std::memory_order_release); // 2. (1) Synchronises with this store.
}

Why is my callback not called?

I'm trying to implement the fuse hello example (https://lastlog.de/misc/fuse-doc/doc/html/hello_8c.html) in C++ (with g++ compiler). I had to change it a little bit to do so.
#define FUSE_USE_VERSION 30
#include <cstdlib>
#include <iostream>
#include <fuse.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
static const char *hello_str = "Hello World!\n";
static int hello_getattr(const char *path, struct stat *stbuf)
{
int res = 0;
memset(stbuf, 0, sizeof(struct stat));
if (strcmp(path, "/") == 0)
{
stbuf->st_mode = S_IFDIR | 0777;
stbuf->st_nlink = 2;
}
else if (strcmp(path, "/hello") == 0)
{
stbuf->st_mode = S_IFREG | 0444;
stbuf->st_nlink = 1;
stbuf->st_size = strlen(hello_str);
}
else
res = -ENOENT;
return res;
};
static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi)
{
(void) offset;
(void) fi;
if (strcmp(path, "/") != 0)
return -ENOENT;
filler(buf, ".", NULL, 0);
filler(buf, "..", NULL, 0);
filler(buf, "hello", NULL, 0);
return 0;
};
static int hello_open(const char *path, struct fuse_file_info *fi)
{
if (strcmp(path, "/hello") != 0)
return -ENOENT;
if ((fi->flags & 3) != O_RDONLY)
return -EACCES;
return 0;
};
static int hello_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
{
size_t len;
(void) fi;
if(strcmp(path, "/hello") != 0)
return -ENOENT;
len = strlen(hello_str);
if (offset < len) {
if (offset + size > len)
size = len - offset;
memcpy(buf, hello_str + offset, size);
} else
size = 0;
return size;
};
static int hello_opendir(const char *path, struct fuse_file_info *fi)
{
return 0;
};
struct hello_fuse_operations : fuse_operations
{
hello_fuse_operations()
{
this->getattr = hello_getattr;
this->open = hello_open;
this->read = hello_read;
this->opendir = hello_opendir;
this->readdir = hello_readdir;
}
};
static struct hello_fuse_operations hello_oper;
int main(int argc, char** argv)
{
return fuse_main_real(argc, argv, &hello_oper, sizeof(&hello_oper), NULL);
}
My problem is, fuse says read and readdir are not implemented and doesn't call them, even though they clearly are implemented.
unique: 13, opcode: READDIR (28), nodeid: 1, insize: 80, pid: 16153
unique: 13, error: -38 (Function not implemented), outsize: 16
It almost seems like I'm doing something basic wrong, but I can't figure out what. (I'm also not very experienced with C++)
How do I get the example to work?
sizeof(&hello_oper) looks like a mistake - this is the size of a pointer, not the size of a struct.
sizeof(fuse_operations) might be what you want. sizeof hello_oper would be the same in this case, but if you later add data members to hello_fuse_operations then it would become wrong.

Why SDL_RWops performs so poorly when writing to file compared to cstdio and std::fstream?

I'm currently in process of migrating my hobby project from std::fstream to SDL_RWops (because SDL_RWops is my only simple choice for loading assets on Android).
Reading from a file works perfectly, but writing to a file is incredibly slow.
Consider following testcases:
C standard IO - 0.217193 secs
std::FILE *io = std::fopen("o.txt", "w");
for (int i = 0; i < 1024*1024*4; i++)
std::putc('0', io);
std::fclose(io);
C++ streams - 0.278278 secs
std::ofstream io("o.txt");
for (int i = 0; i < 1024*1024*4; i++)
io << '0';
io.close();
SDL_RWops: - 17.9893 secs
SDL_RWops *io = SDL_RWFromFile("o.txt", "w");
for (int i = 0; i < 1024*1024*4; i++)
io->write(io, "0", 1, 1);
io->close(io);
All testcases were compiled with g++ 5.3.0 (mingw-w64) x86 with -O3. I've used SDL 2.0.4.
I've also tried -O0 with similar results (0.02 to 0.25 seconds slower).
After looking at these results I have an obvious questions:
Why SDL_RWops writing performance is so poor?
What can I do to make it perform better?
Edit: Here is the code of windows_file_write() (from SDL), which is what io->write should point to. It should do buffered output, but I'm not sure how it works.
static size_t SDLCALL
windows_file_write(SDL_RWops * context, const void *ptr, size_t size, size_t num)
{
size_t total_bytes;
DWORD byte_written;
size_t nwritten;
total_bytes = size * num;
if (!context || context->hidden.windowsio.h == INVALID_HANDLE_VALUE || total_bytes <= 0 || !size)
return 0;
if (context->hidden.windowsio.buffer.left) {
SetFilePointer(context->hidden.windowsio.h,
-(LONG)context->hidden.windowsio.buffer.left, NULL,
FILE_CURRENT);
context->hidden.windowsio.buffer.left = 0;
}
/* if in append mode, we must go to the EOF before write */
if (context->hidden.windowsio.append) {
if (SetFilePointer(context->hidden.windowsio.h, 0L, NULL, FILE_END) ==
INVALID_SET_FILE_POINTER) {
SDL_Error(SDL_EFWRITE);
return 0;
}
}
if (!WriteFile
(context->hidden.windowsio.h, ptr, (DWORD)total_bytes, &byte_written, NULL)) {
SDL_Error(SDL_EFWRITE);
return 0;
}
nwritten = byte_written / size;
return nwritten;
}
In short: I've managed to improve it. Now I'm getting 0.316382 secs, which is only a bit slower than other solutions.
But it's one of the dirtiest hacks I've ever done in my life. I'd appreciate any better solutions.
How it was done: I've rolled custom replacement for SDL_RWFromFile(): I've copy-pasted the implementation from SDL_rwops.c and removed all preprocessor branches as if only HAVE_STDIO_H was defined. The function contained a call to SDL_RWFromFP(), thus I've copy-pasted SDL_RWFromFP() too and applied same modifications to it. In turn, SDL_RWFromFP() relied on stdio_size(),stdio_read(),stdio_write(),stdio_seek() and stdio_close() (these are a part of SDL_rwops.c too), thus I've copy-pasted them too. In turn, these relied (again!) on some fields of "hidden" union inside of struct SDL_RWops, which are disabled on windows using preprocessor. Instead of changing the header, I've changed the copy-pasted code to use different members of "hidden" union, which do exist on windows. (It's safe, because nothing except my own and copy-pasted code touches the struct.) Some other tweaks were made to make the code work as C++ instead of C.
This is what I got:
#if OnWindows
#define hidden_stdio_fp ((FILE * &)context->hidden.windowsio.h)
#define hidden_stdio_autoclose ((SDL_bool &)context->hidden.windowsio.append)
// ** Begin copied code **
static auto stdio_size = [](SDL_RWops * context) -> int64_t
{
int64_t pos, size;
pos = SDL_RWseek(context, 0, RW_SEEK_CUR);
if (pos < 0) {
return -1;
}
size = SDL_RWseek(context, 0, RW_SEEK_END);
SDL_RWseek(context, pos, RW_SEEK_SET);
return size;
};
static auto stdio_seek = [](SDL_RWops * context, int64_t offset, int whence) -> int64_t
{
#ifdef HAVE_FSEEKO64
if (std::fseeko64(hidden_stdio_fp, (off64_t)offset, whence) == 0) {
return std::ftello64(hidden_stdio_fp);
}
#elif defined(HAVE_FSEEKO)
if (std::fseeko(hidden_stdio_fp, (off_t)offset, whence) == 0) {
return std::ftello(hidden_stdio_fp);
}
#elif defined(HAVE__FSEEKI64)
if (std::_fseeki64(hidden_stdio_fp, offset, whence) == 0) {
return std::_ftelli64(hidden_stdio_fp);
}
#else
if (std::fseek(hidden_stdio_fp, offset, whence) == 0) {
return std::ftell(hidden_stdio_fp);
}
#endif
return SDL_Error(SDL_EFSEEK);
};
static auto stdio_read = [](SDL_RWops * context, void *ptr, std::size_t size, std::size_t maxnum) -> std::size_t
{
std::size_t nread;
nread = std::fread(ptr, size, maxnum, hidden_stdio_fp);
if (nread == 0 && std::ferror(hidden_stdio_fp)) {
SDL_Error(SDL_EFREAD);
}
return nread;
};
static auto stdio_write = [](SDL_RWops * context, const void *ptr, std::size_t size, std::size_t num) -> std::size_t
{
std::size_t nwrote;
nwrote = std::fwrite(ptr, size, num, hidden_stdio_fp);
if (nwrote == 0 && std::ferror(hidden_stdio_fp)) {
SDL_Error(SDL_EFWRITE);
}
return nwrote;
};
static auto stdio_close = [](SDL_RWops * context) -> int
{
int status = 0;
if (context) {
if (hidden_stdio_autoclose) {
/* WARNING: Check the return value here! */
if (std::fclose(hidden_stdio_fp) != 0) {
status = SDL_Error(SDL_EFWRITE);
}
}
SDL_FreeRW(context);
}
return status;
};
static auto RWFromFP = [](FILE * fp, SDL_bool autoclose) -> SDL_RWops *
{
SDL_RWops *context = 0;
context = SDL_AllocRW();
if (context != 0) {
context->size = stdio_size;
context->seek = stdio_seek;
context->read = stdio_read;
context->write = stdio_write;
context->close = stdio_close;
hidden_stdio_fp = fp;
hidden_stdio_autoclose = autoclose;
context->type = SDL_RWOPS_STDFILE;
}
return context;
};
static auto SDL_RWFromFile = [](const char *file, const char *mode) -> SDL_RWops *
{
SDL_RWops *context = 0;
if (!file || !*file || !mode || !*mode) {
SDL_SetError("SDL_RWFromFile(): No file or no mode specified");
return 0;
}
FILE *fp = std::fopen(file, mode);
if (fp == 0) {
SDL_SetError("Couldn't open %s", file);
} else {
context = RWFromFP(fp, (SDL_bool)1);
}
return context;
};
// ** End copied code **
#undef hidden_stdio_fp
#undef hidden_stdio_autoclose
#endif

How to asynchronously read/write in C++?

How do you copy one stream to another using dedicated read/write threads in C++?
Let's say I have these methods (not real, but to illustrate the point) to read/write data from. These read/write functions could represent anything (network/file/USB/serial/etc).
// returns the number of bytes read
void read(char* buffer, int bufferSize, int* bytesRead);
// returns the number of bytes written
void write(char* buffer, int bufferSize, int* bytesWritten);
The solution should also be portable.
NOTE: I am aware that Windows has a FILE_FLAG_OVERLAPPED feature, but this assumes that the read/write is file IO. Remember, these read/write methods could represent anything.
Here is the solution I came up with.
Header
#pragma once
#include <stdlib.h>
#include <queue>
#include <mutex>
#include <thread>
#include <chrono>
#include <list>
#include <thread>
#define ASYNC_COPY_READ_WRITE_SUCCESS 0
struct BufferBlock;
struct ReadStream
{
// read a stream to a buffer.
// return non-zero if error occured
virtual int read(char* buffer, int bufferSize, int* bytesRead) = 0;
};
struct WriteStream
{
// write a buffer to a stream.
// return non-zero if error occured
virtual int write(char* buffer, int bufferSize, int* bytesWritten) = 0;
};
class BufferBlockManager
{
public:
BufferBlockManager(int numberOfBlocks, int bufferSize);
~BufferBlockManager();
void enqueueBlockForRead(BufferBlock* block);
void dequeueBlockForRead(BufferBlock** block);
void enqueueBlockForWrite(BufferBlock* block);
void dequeueBlockForWrite(BufferBlock** block);
void resetState();
private:
std::list<BufferBlock*> blocks;
std::queue<BufferBlock*> blocksPendingRead;
std::queue<BufferBlock*> blocksPendingWrite;
std::mutex queueLock;
std::chrono::milliseconds dequeueSleepTime;
};
void AsyncCopyStream(BufferBlockManager* bufferBlockManager, ReadStream* readStream, WriteStream* writeStream, int* readResult, int* writeResult);
CPP
#include "AsyncReadWrite.h"
struct BufferBlock
{
BufferBlock(int bufferSize) : buffer(NULL)
{
this->bufferSize = bufferSize;
this->buffer = new char[bufferSize];
this->actualSize = 0;
this->isLastBlock = false;
}
~BufferBlock()
{
this->bufferSize = 0;
free(this->buffer);
this->buffer = NULL;
this->actualSize = 0;
}
char* buffer;
int bufferSize;
int actualSize;
bool isLastBlock;
};
BufferBlockManager::BufferBlockManager(int numberOfBlocks, int bufferSize)
{
dequeueSleepTime = std::chrono::milliseconds(100);
for (int x = 0; x < numberOfBlocks; x++)
{
BufferBlock* block = new BufferBlock(bufferSize);
blocks.push_front(block);
blocksPendingRead.push(block);
}
}
BufferBlockManager::~BufferBlockManager()
{
for (std::list<BufferBlock*>::const_iterator iterator = blocks.begin(), end = blocks.end(); iterator != end; ++iterator) {
delete (*iterator);
}
}
void BufferBlockManager::enqueueBlockForRead(BufferBlock* block)
{
queueLock.lock();
block->actualSize = 0;
block->isLastBlock = false;
blocksPendingRead.push(block);
queueLock.unlock();
}
void BufferBlockManager::dequeueBlockForRead(BufferBlock** block)
{
WAITFOR:
while (blocksPendingRead.size() == 0)
std::this_thread::sleep_for(dequeueSleepTime);
queueLock.lock();
if (blocksPendingRead.size() == 0)
{
queueLock.unlock();
goto WAITFOR;
}
*block = blocksPendingRead.front();
blocksPendingRead.pop();
queueLock.unlock();
}
void BufferBlockManager::enqueueBlockForWrite(BufferBlock* block)
{
queueLock.lock();
blocksPendingWrite.push(block);
queueLock.unlock();
}
void BufferBlockManager::dequeueBlockForWrite(BufferBlock** block)
{
WAITFOR:
while (blocksPendingWrite.size() == 0)
std::this_thread::sleep_for(dequeueSleepTime);
queueLock.lock();
if (blocksPendingWrite.size() == 0)
{
queueLock.unlock();
goto WAITFOR;
}
*block = blocksPendingWrite.front();
blocksPendingWrite.pop();
queueLock.unlock();
}
void BufferBlockManager::resetState()
{
queueLock.lock();
blocksPendingRead = std::queue<BufferBlock*>();
blocksPendingWrite = std::queue<BufferBlock*>();
for (std::list<BufferBlock*>::const_iterator iterator = blocks.begin(), end = blocks.end(); iterator != end; ++iterator) {
(*iterator)->actualSize = 0;
}
queueLock.unlock();
}
struct AsyncCopyContext
{
AsyncCopyContext(BufferBlockManager* bufferBlockManager, ReadStream* readStream, WriteStream* writeStream)
{
this->bufferBlockManager = bufferBlockManager;
this->readStream = readStream;
this->writeStream = writeStream;
this->readResult = ASYNC_COPY_READ_WRITE_SUCCESS;
this->writeResult = ASYNC_COPY_READ_WRITE_SUCCESS;
}
BufferBlockManager* bufferBlockManager;
ReadStream* readStream;
WriteStream* writeStream;
int readResult;
int writeResult;
};
void ReadStreamThread(AsyncCopyContext* asyncContext)
{
int bytesRead = 0;
BufferBlock* readBuffer = NULL;
int readResult = ASYNC_COPY_READ_WRITE_SUCCESS;
while (
// as long there hasn't been any write errors
asyncContext->writeResult == ASYNC_COPY_READ_WRITE_SUCCESS
// and we haven't had an error reading yet
&& readResult == ASYNC_COPY_READ_WRITE_SUCCESS)
{
// let's deque a block to read to!
asyncContext->bufferBlockManager->dequeueBlockForRead(&readBuffer);
readResult = asyncContext->readStream->read(readBuffer->buffer, readBuffer->bufferSize, &bytesRead);
readBuffer->actualSize = bytesRead;
readBuffer->isLastBlock = bytesRead == 0;
if (readResult == ASYNC_COPY_READ_WRITE_SUCCESS)
{
// this was a valid read, go ahead and queue it for writing
asyncContext->bufferBlockManager->enqueueBlockForWrite(readBuffer);
}
else
{
// an error occured reading
asyncContext->readResult = readResult;
// since an error occured, lets queue an block to write indicatiting we are done and there are no more bytes to read
readBuffer->isLastBlock = true;
readBuffer->actualSize = 0;
asyncContext->bufferBlockManager->enqueueBlockForWrite(readBuffer);
}
if (readBuffer->isLastBlock) return;
}
}
void WriteStreamThread(AsyncCopyContext* asyncContext)
{
int bytesWritten = 0;
BufferBlock* writeBuffer = NULL;
int writeResult = ASYNC_COPY_READ_WRITE_SUCCESS;
bool isLastWriteBlock = false;
while (
// as long as there are no errors during reading
asyncContext->readResult == ASYNC_COPY_READ_WRITE_SUCCESS
// and we haven't had an error writing yet
&& writeResult == ASYNC_COPY_READ_WRITE_SUCCESS)
{
// lets dequeue a block for writing!
asyncContext->bufferBlockManager->dequeueBlockForWrite(&writeBuffer);
isLastWriteBlock = writeBuffer->isLastBlock;
if (writeBuffer->actualSize > 0)
writeResult = asyncContext->writeStream->write(writeBuffer->buffer, writeBuffer->actualSize, &bytesWritten);
if (writeResult == ASYNC_COPY_READ_WRITE_SUCCESS)
{
asyncContext->bufferBlockManager->enqueueBlockForRead(writeBuffer);
if (isLastWriteBlock) return;
}
else
{
asyncContext->writeResult = writeResult;
asyncContext->bufferBlockManager->enqueueBlockForRead(writeBuffer);
return;
}
}
}
void AsyncCopyStream(BufferBlockManager* bufferBlockManager, ReadStream* readStream, WriteStream* writeStream, int* readResult, int* writeResult)
{
AsyncCopyContext asyncContext(bufferBlockManager, readStream, writeStream);
std::thread readThread(ReadStreamThread, &asyncContext);
std::thread writeThread(WriteStreamThread, &asyncContext);
readThread.join();
writeThread.join();
*readResult = asyncContext.readResult;
*writeResult = asyncContext.writeResult;
}
Usage
#include <stdio.h>
#include <tchar.h>
#include "AsyncReadWrite.h"
struct ReadTestStream : ReadStream
{
int readCount = 0;
int read(char* buffer, int bufferSize, int* bytesRead)
{
printf("Starting read...\n");
memset(buffer, bufferSize, 0);
if (readCount == 10)
{
*bytesRead = 0;
return 0;
}
// pretend this function takes a while!
std::this_thread::sleep_for(std::chrono::milliseconds(100));
char buff[100];
sprintf_s(buff, "This is read number %d\n", readCount);
strcpy_s(buffer, sizeof(buff), buff);
*bytesRead = strlen(buffer);
readCount++;
printf("Finished read...\n");
return 0;
}
};
struct WriteTestStream : WriteStream
{
int write(char* buffer, int bufferSize, int* bytesWritten)
{
printf("Starting write...\n");
// pretend this function takes a while!
std::this_thread::sleep_for(std::chrono::milliseconds(500));
printf(buffer);
printf("Finished write...\n");
return 0;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
BufferBlockManager bufferBlockManager(5, 4096);
ReadTestStream readStream;
WriteTestStream writeStream;
int readResult = 0;
int writeResult = 0;
printf("Starting copy...\n");
AsyncCopyStream(&bufferBlockManager, &readStream, &writeStream, &readResult, &writeResult);
printf("Finished copy... readResult=%d writeResult=%d \n", readResult, writeResult);
getchar();
return 0;
}
EDIT: I put my solution into a GitHub repository here. If you wish to use this code, refer to the repository since it may be more updated than this answer.
Typically, you would just have one thread for each direction that alternates between reads and writes.

Multiprocess c++(11) with linked list pointer as global variable

I have the classic problem as given here, here and here and also here,
However, I would like a child process to insert an element at the end of a doubly linked list. a point to the first element of the list is global, and I want to access all of the list elements form the main process, and also next time I branch the main using fork, I want to be abe to access all elements, and update them, and insert more elements, in turn the main process again being able to access the modifyied list.
Each process exits with a system call with execvp (i need them to be able to call stuff using varied number of arameters).
I perhaps am asking a too broad question, but I personally did not get any further than branching and the inserting an element at the end of the list. Thus I actually dont have a single line of code that takes me where I want to go. I have no idea how to use shm() in this scenario.
Please help.
You can try this.. I just wrote it from scratch.. It is cross-platform so that's always a plus. The allocators and pools can be re-used with anything. For example, you can make any stl container allocate on the stack or wherever you want..
SharedMemory.hpp:
#ifndef SHAREDMEMORY_HPP_INCLUDED
#define SHAREDMEMORY_HPP_INCLUDED
#if defined _WIN32 || defined _WIN64
#include <windows.h>
#else
#include <sys/types.h>
#include <sys/mman.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <unistd.h>
#endif
#include <string>
#include <cstdint>
class SharedMemory
{
private:
std::string name;
std::size_t size;
void* data;
void* hFileMap;
public:
SharedMemory(std::string name, std::size_t size) : name(name), size(size), data(nullptr), hFileMap(nullptr) {};
~SharedMemory();
bool Open();
bool Create();
std::size_t GetSize() const {return this->size;}
void* GetPointer() const {return this->data;}
};
#endif // SHAREDMEMORY_HPP_INCLUDED
SharedMemory.cpp:
#include "SharedMemory.hpp"
SharedMemory::~SharedMemory()
{
if (data)
{
#if defined _WIN32 || defined _WIN64
UnmapViewOfFile(data);
data = nullptr;
if (hFileMap)
{
if (CloseHandle(hFileMap))
{
hFileMap = nullptr;
}
}
#else
if (data)
{
munmap(data, size);
data = nullptr;
}
if (hFileMap)
{
if (!close(hFileMap))
{
hFileMap = nullptr;
}
}
#endif
}
}
bool SharedMemory::Open()
{
#if defined _WIN32 || defined _WIN64
if ((hFileMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, false, name.c_str())) == nullptr)
{
return false;
}
if ((data = MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, size)) == nullptr)
{
CloseHandle(hFileMap);
return false;
}
#else
if ((hFileMap = open(MapName.c_str(), O_RDWR | O_CREAT, 438)) == -1)
{
return false;
}
if ((data = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, hFileMap, 0)) == MAP_FAILED)
{
close(hFileMap);
return false;
}
#endif
return true;
}
bool SharedMemory::Create()
{
#if defined _WIN32 || defined _WIN64
if ((hFileMap = CreateFileMapping(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, size, name.c_str())) == nullptr)
{
return false;
}
if ((data = MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, size)) == nullptr)
{
CloseHandle(hFileMap);
return false;
}
#else
if ((hFileMap = open(MapName.c_str(), O_RDWR | O_CREAT, 438)) == -1)
{
return false;
}
if ((data = mmap(nullptr, Size, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, hFileMap, 0)) == MAP_FAILED)
{
close(hFileMap);
return false;
}
#endif
return true;
}
Pools.hpp:
#ifndef POOLS_HPP_INCLUDED
#define POOLS_HPP_INCLUDED
#include <stdexcept>
#include <cstdint>
#include "SharedMemory.hpp"
template<typename T>
class SharedPool
{
private:
T* data;
SharedMemory* shm;
std::size_t size;
public:
SharedPool(SharedMemory* shm) : data(reinterpret_cast<T*>(shm->GetPointer())), shm(shm), size(shm->GetSize()) {};
template<typename U = T>
void* allocate(std::size_t n, const void* hint = 0) {return &data[0];}
template<typename U = T>
void deallocate(U* ptr, std::size_t n) {}
template<typename U = T>
std::size_t max_size() const {return size;}
};
#endif // POOLS_HPP_INCLUDED
main.cpp (adding values to shared memory from process one):
#include "SharedMemory.hpp"
#include "Allocators.hpp"
#include "Pools.hpp"
#include <vector>
#include <iostream>
int main()
{
SharedMemory mem = SharedMemory("Local\\Test_Shared_Memory", 1024);
if (!mem.Open() && !mem.Create())
{
throw std::runtime_error("Error Mapping Shared Memory!");
}
auto pool = PoolAllocator<int, SharedPool<int>>(SharedPool<int>(&mem));
std::vector<int, decltype(pool)> v(pool);
int* ptr = reinterpret_cast<int*>(mem.GetPointer());
std::cout<<"Pushing 3 values to: "<<ptr<<"\n";
v.push_back(100);
v.push_back(200);
v.push_back(700);
std::cin.get();
}
main.cpp (Reading values from shared memory process two):
#include "SharedMemory.hpp"
#include "Allocators.hpp"
#include "Pools.hpp"
#include <vector>
#include <iostream>
int main()
{
SharedMemory mem = SharedMemory("Local\\Test_Shared_Memory", 1024);
if (!mem.Open() && !mem.Create())
{
throw std::runtime_error("Error Mapping Shared Memory!");
}
auto pool = PoolAllocator<int, SharedPool<int>>(SharedPool<int>(&mem));
std::vector<int, decltype(pool)> v(pool);
int* ptr = reinterpret_cast<int*>(mem.GetPointer());
std::cout<<"Reading 3 values from: "<<ptr<<"\n";
v.reserve(3);
std::cout<<v[0]<<"\n";
std::cout<<v[1]<<"\n";
std::cout<<v[2]<<"\n";
std::cin.get();
}
This is a tough problem.
One way is to use Shared Memory and when you build the linked list, give it your own allocator, to use the shared memory. Other way is to implement your own linked list based on the shared memory.
You can also try to use boost - Boost interprocess, which is probably the ideal solution.
Specifically speaking - interprocess with containers