Is this piece of code correct? - c++

I compiled it on Linux with: g++ test.c -o test
I rewritten the original example.
Now made the first process to wait 2 seconds, (so that process2 could write on the shared memory), then I made process1 to read from that memory. Is this test correct?
Secondo question: where should I put:
shmdt(tests[0]); // or 1
shmctl(statesid, IPC_RMID, 0);
//Global scope
char *state[2];
//...
//...
struct teststruct {
int stateid;
teststruct *next;
//other things
};
void write(teststruct &t, char* what)
{
strcpy(state[t.next->stateid], what);
printf("\n\nI am (%d), I wrote on: %d", t.stateid, t.next->stateid);
}
void read(teststruct &t)
{
printf("\n\nI am (%d), I read: **%s**", t.stateid, state[t.stateid]);
}
int main() {
key_t key;
if ((key = ftok(".", 'a')) == -1) {
perror("ftok");
exit(1);
}
int statesid;
if ((statesid = shmget(key, sizeof(char*)*50, 0600 | IPC_CREAT )) == -1) {
perror("shmget error");
exit(1);
}
state[0] = (char*)shmat(statesid, NULL, 0);
state[1] = (char*)shmat(statesid, NULL, 0);
teststruct tests[2];
tests[0].stateid = 0;
tests[0].next = &tests[1];
tests[1].stateid = 1;
tests[1].next = &tests[0];
int t0, t1;
switch (t0 = fork()) {
case (0):
sleep(2);
read(tests[0]);
exit(0);
case (-1):
printf("\nError!");
exit(-1);
default:
wait();
}
switch (t1 = fork()) {
case (0):
write(tests[1], "1 write on 0 in theory.");
exit(0);
case (-1):
printf("\nError!");
exit(-1);
default:
wait();
}
return 0;
}
In particular I am asking if "state" is really shared between the two process, and If what I've done is a good way to do that.
My goal is to make char *state[2] shared (reading/modifying) between the two processes after fork.

You don't need to call shmat() twice. You've only allocated enough space for two pointers, so you can't communicate much between the two processes. And you can't rely on being able to copy a pointer to memory in the first process into shared memory and then have the second process read and use it. The address may be valid in the first process and not in the second; it may well point at completely different data in the second process (dynamic memory allocation in particular could screw this up). You can only rely on the contents of the shared memory being the same in both processes. You should allocate enough shared memory to hold the shared data.
However, with that said, the two processes should be sharing that small piece of shared memory, and in both processes, state[0] and state[1] will point at the shared memory and you should be able to communicate between the two by writing in the shared memory. Note that after forking, if either process changes the value stored in its state[0] or state[1], the other process will not see that change — the other process can only see what changes in the shared memory those pointers point to.
Of course, you've not set up any synchronization mechanism, so the access will likely be chaotic.
How can I modify my code just to make it works as intended (without considering synchronization issues)?
It isn't entirely clear how it is intended to work, which complicates answering the question. However, if you want (for sake of example) the child process to write a word to the shared memory and the parent process to read the word from shared memory, then you allocate enough shared memory for the biggest word you're willing to process, then arrange for the child to copy a word from its per-process memory into the shared memory (and notify the parent that it has done so), and then the parent can copy or read the word from shared memory and compare it with data from its per-process memory.
Because you have a parent-child process which are forks of the same process, you will find that the two processes share a lot of the same memory addresses containing the same information. This is, however, coincidental. You can have unrelated processes connect to shared memory, and they need not have any addresses in common. Thus, it would be trivial to get spurious results from your current setup.
Working Code
For some definitions of 'working', the following C++ code does. The code is subtly C++; the code assumes struct teststruct declares type teststruct, and uses references as parameters.
Note that the (revised) code in the question has its wait() calls infelicitously placed.
shm2.cpp
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
static char *state = 0;
struct teststruct
{
int stateid;
teststruct *next;
};
void sm_write(teststruct &t, char* /*what*/)
{
//strcpy(state[t.next->stateid], what);
printf("[%5d] I am (%d), I wrote on: %d\n", (int)getpid(), t.stateid, t.next->stateid);
}
void sm_read(teststruct &t)
{
printf("[%5d] I am (%d), I read: **%s**\n", (int)getpid(), t.stateid, state);
}
int main(void)
{
key_t key;
if ((key = ftok(".", 'a')) == -1) {
perror("ftok");
exit(1);
}
int statesid;
if ((statesid = shmget(key, sizeof(char)*512, 0600 | IPC_CREAT )) == -1) {
perror("shmget error");
exit(1);
}
if ((state = (char*)shmat(statesid, NULL, 0)) == 0)
{
perror("shmat");
exit(1);
}
sprintf(state, "This is a string in shared memory %d", 919);
teststruct tests[2];
tests[0].stateid = 0;
tests[0].next = &tests[1];
tests[1].stateid = 0;
tests[1].next = &tests[0];
int t0, t1;
if ((t0 = fork()) < 0)
{
perror("fork-1");
exit(1);
}
else if (t0 == 0)
{
sm_read(tests[0]);
printf("[%5d] sleeping\n", (int)getpid());
sleep(2);
printf("[%5d] waking\n", (int)getpid());
sm_read(tests[0]);
exit(0);
}
else if ((t1 = fork()) < 0)
{
perror("fork-2");
exit(-1);
}
else if (t1 == 0)
{
printf("[%5d] sleeping\n", (int)getpid());
sleep(1);
printf("[%5d] waking\n", (int)getpid());
strcpy(state, "1 write on 0 in theory.");
sm_write(tests[1], state);
exit(0);
}
int corpse;
int status;
while ((corpse = wait(&status)) > 0)
printf("PID %5d died with status 0x%.4X\n", corpse, status);
return 0;
}
Example run
[20440] sleeping
[20440] waking
[20440] I am (0), I wrote on: 0
[20439] I am (0), I read: **This is a string in shared memory 919**
[20439] sleeping
[20439] waking
[20439] I am (0), I read: **1 write on 0 in theory.**
PID 20440 died with status 0x0000
PID 20439 died with status 0x0000

You have a problem with the size of the shared memory. In:
(statesid = shmget(key, sizeof(char*)*2, 0600 | IPC_CREAT )
you are just reserving space for 2 pointers to char. You need to allocate enough space for all your data, that based on the struct is kind of linked structure. The code could be something like the following, though the purpose of the fork() and shared memory is not very clear to me:
struct teststruct {
int stateid;
teststruct *next;
//other things
};
void dosomething(teststruct &t){
//forget about global space, you don't need it
}
int main() {
key_t key;
if ((key = ftok(".", 'a')) == -1) {
perror("ftok");
exit(1);
}
int statesid;
int size_struct = sizeof(teststruct)*2; //assuming you will have only 1 level of linking
if ((statesid = shmget(key, size_struct, 0600 | IPC_CREAT )) == -1) {
perror("shmget error");
exit(1);
}
//if you need to hold just one teststruct object data, you can do
teststruct* p_test_struct = (teststruct*)shmat(statesid, NULL, 0);
for (int i=0; i<2; i++){
*p_test_struct = tests[i]; //this actually writes tests[i] into shared mem
int t0, t1;
switch (t0 = fork()) {
case (0):
dosomething(*p_test_struct);
exit(0);
case (-1):
printf("\nError!");
exit(-1);
default:
wait();
}
}
return 0;
}

No, it does not. Because you are using fork (multiprocess) instead of threads (multithread). Memory zones are not shared into parent and child process. You will have the same value into it on the child but after that it will be independent to the another one.

Related

Why am I segfaulting in this specific code?

This program is supposed to take a file name and argument(s) and create a process that executes the code while outputting the result to the terminal (which I don't know why that isn't working either).
I have found that the seg fault is coming from my attempt to free the argvNew array of strings
#include <iostream>
using namespace std;
#include <unistd.h>
#include <sys/wait.h>
main(int argc, char **argv){
int pid;
int i;
char *argvNew[argc-1];
do{
//Check for failure
if ((pid = fork()) < 0) {
cerr << "Fork error\n";
exit(1);
}
//Check if parent
else if (pid == 0) {
/* child process */
//Add arguments to new array
for(i = 0; i < argc-2; i++){
argvNew[i] = argv[i+1];
}
argvNew[argc-2] = NULL;
if (execvp(argv[1], argvNew) < 0) {
cerr << "Execve error\n";
exit(1);
}
}
else {
/* parent */
waitpid(pid, NULL, 0);/* wait for the child to finish */
//Free argvNew
for(i = 0; i < argc-1;i++){
free(argvNew[i]);
}
free(argvNew);
}
//if we're need to create a new list of args in the future put it here
}while(!argc);
}
test input: ./myProgram /bin/ls -l
argvNew is automaticlly allocated, which means that the resources held by it are released automagiclly when it goes out of scope. You only need to free dynamicly allocated arrays:
char a[50]; // the [50] means automatic allocation
// ...
// no need to free
char* a = malloc(50); // dynamic allocation
// ...
// need to free later, or memory leak
free(a);
for(i = 0; i < argc-1;i++){
free(argvNew[i]);
}
//This next call is identical to free(argvNew[0]), probably where you're
//segfaulting, since you're freeing something twice
free(argvNew);
Note that you don't need to call free(), since argvNew[] contains pointers that do not point to new/malloc'ed data, but rather to the values of the argv array, which are managed by the parent process and should not be explicitly free()'ed by you

multiple buffers using threads

I need some algorithm help with a multithreaded program I'm writing. It's basically the cp command in unix, but with a read thread and a write thread. I'm using semaphores for thread synchronization. I have structs for buffer and thread data defined as
struct bufType {
char buf[BUFFER_SIZE];
int numBytes;
};
struct threadData {
int fd;
bufType buf;
};
and a global array of bufType. Code for my main is
int main(int argc, const char * argv[])
{
int in, out;
pthread_t Producer, Consumer;
threadData producerData, consumerData;
if (argc != 3)
{
cout << "Error: incorrect number of params" << endl;
exit(0);
}
if ((in = open(argv[1], O_RDONLY, 0666)) == -1)
{
cout << "Error: cannot open input file" << endl;
exit(0);
}
if ((out = open(argv[2], O_WRONLY | O_CREAT, 0666)) == -1)
{
cout << "Cannot create output file" << endl;
exit(0);
}
sem_init(&sem_empty, 0, NUM_BUFFERS);
sem_init(&sem_full, 0, 0);
pthread_create (&Producer, NULL, read_thread, (void *) &producerData);
pthread_create (&Consumer, NULL, write_thread, (void *) &consumerData);
pthread_join(Producer, NULL);
pthread_join(Consumer, NULL);
return 0;
}
and read and write threads:
void *read_thread(void *data)
{
threadData *thread_data;
thread_data = (threadData *) data;
while((thread_data->buf.numBytes = slow_read(thread_data->fd, thread_data->buf.buf, BUFFER_SIZE)) != 0)
{
sem_post(&sem_full);
sem_wait(&sem_empty);
}
pthread_exit(0);
}
void *write_thread(void *data)
{
threadData *thread_data;
thread_data = (threadData *) data;
sem_wait(&sem_full);
slow_write(thread_data->fd, thread_data->buf.buf, thread_data->buf.numBytes);
sem_post(&sem_empty);
pthread_exit(0);
}
So my issue is in what to assign to my threadData variables in main, and my semaphore logic in the read and write threads. I appreciate any help you're able to give
Being a windows guy who does not use file descriptors I might be wrong with the in's and out's but I think this needs to be done in your main in order to setup the threadData structures.
producerData.fd = in;
consumerData.fd = out;
Then declare ONE SINGLE object of type bufType for both structures. Change for example the definition of threadData to
struct threadData {
int fd;
bufType* buf;
};
and in your Main, you write
bufType buffer;
producerData.buf = &buffer;
consumerData.buf = &buffer;
Then both threads will use a common buffer. Otherwise you would be writing to the producerData buffer, but the consumerData buffer will stay empty (and this is where your writer thread is looking for data)
Then you need to change your signalling logic. Right now your program cannot accept input that exceeds BUFFER_SIZE, because your write thread will only write once. There needs to be a loop around it. And then you need some mechanism that signals the writer thread that no more data will be sent. For example you could do this
void *read_thread(void *data)
{
threadData *thread_data;
thread_data = (threadData *) data;
while((thread_data->buf->numBytes = slow_read(thread_data->fd, thread_data->buf->buf, BUFFER_SIZE)) > 0)
{
sem_post(&sem_full);
sem_wait(&sem_empty);
}
sem_post(&sem_full); // Note that thread_data->buf->numBytes <= 0 now
pthread_exit(0);
}
void *write_thread(void *data)
{
threadData *thread_data;
thread_data = (threadData *) data;
sem_wait(&sem_full);
while (thread_data->buf->numBytes > 0)
{
slow_write(thread_data->fd, thread_data->buf->buf, thread_data->buf->numBytes);
sem_post(&sem_empty);
sem_wait(&sem_full);
}
pthread_exit(0);
}
Hope there are no more errors, did not test solution. But the concept should be what you were asking for.
You could use a common buffer pool, either a circular array or a linked lists. Here is a link to a zip of a Windows example that is similar to what you're asking, using linked lists as part of a inter-thread messaging system to buffer data. Other than the creation of the mutexes, semaphores, and the write thread, the functions are small and simple. mtcopy.zip .

C: performance of pthread, low than single thrad

I'm confusing about the performance of my code, when dealing with single thread it only using 13s, but it's will consume 80s. I don't know whether the vector can only be accessed by one thread at a time, if so it's likely I have to use a struct array to store data instead of vector, could anyone kindly help?
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <iterator>
#include <string>
#include <ctime>
#include <bangdb/database.h>
#include "SEQ.h"
#define NUM_THREADS 16
using namespace std;
typedef struct _thread_data_t {
std::vector<FDT> *Query;
unsigned long start;
unsigned long end;
connection* conn;
int thread;
} thread_data_t;
void *thr_func(void *arg) {
thread_data_t *data = (thread_data_t *)arg;
std::vector<FDT> *Query = data->Query;
unsigned long start = data->start;
unsigned long end = data->end;
connection* conn = data->conn;
printf("thread %d started %lu -> %lu\n", data->thread, start, end);
for (unsigned long i=start;i<=end ;i++ )
{
FDT *fout = conn->get(&((*Query).at(i)));
if (fout == NULL)
{
//printf("%s\tNULL\n", s);
}
else
{
printf("Thread:%d\t%s\n", data->thread, fout->data);
}
}
pthread_exit(NULL);
}
int main(int argc, char *argv[])
{
if (argc<2)
{
printf("USAGE: ./seq <.txt>\n");
printf("/home/rd/SCRIPTs/12X18610_L5_I052.R1.clean.code.seq\n");
exit(-1);
}
printf("%s\n", argv[1]);
vector<FDT> Query;
FILE* fpin;
if((fpin=fopen(argv[1],"r"))==NULL) {
printf("Can't open Input file %s\n", argv[1]);
return -1;
}
char *key = (char *)malloc(36);
while (fscanf(fpin, "%s", key) != EOF)
{
SEQ * sequence = new SEQ(key);
FDT *fk = new FDT( (void*)sequence, sizeof(*sequence) );
Query.push_back(*fk);
}
unsigned long Querysize = (unsigned long)(Query.size());
std::cout << "myvector stores " << Querysize << " numbers.\n";
//create database, table and connection
database* db = new database((char*)"berrydb");
//get a table, a new one or existing one, walog tells if log is on or off
table* tbl = db->gettable((char*)"hg19", JUSTOPEN);
if(tbl == NULL)
{
printf("ERROR:table NULL error");
exit(-1);
}
//get a new connection
connection* conn = tbl->getconnection();
if(conn == NULL)
{
printf("ERROR:connection NULL error");
exit(-1);
}
cerr<<"begin querying...\n";
time_t begin, end;
double duration;
begin = clock();
unsigned long ThreadDealSize = Querysize/NUM_THREADS;
cerr<<"Querysize:"<<ThreadDealSize<<endl;
pthread_t thr[NUM_THREADS];
int rc;
thread_data_t thr_data[NUM_THREADS];
for (int i=0;i<NUM_THREADS ;i++ )
{
unsigned long ThreadDealStart = ThreadDealSize*i;
unsigned long ThreadDealEnd = ThreadDealSize*(i+1) - 1;
if (i == (NUM_THREADS-1) )
{
ThreadDealEnd = Querysize-1;
}
thr_data[i].conn = conn;
thr_data[i].Query = &Query;
thr_data[i].start = ThreadDealStart;
thr_data[i].end = ThreadDealEnd;
thr_data[i].thread = i;
}
for (int i=0;i<NUM_THREADS ;i++ )
{
if (rc = pthread_create(&thr[i], NULL, thr_func, &thr_data[i]))
{
fprintf(stderr, "error: pthread_create, rc: %d\n", rc);
return EXIT_FAILURE;
}
}
for (int i = 0; i < NUM_THREADS; ++i) {
pthread_join(thr[i], NULL);
}
cerr<<"done\n"<<endl;
end = clock();
duration = double(end - begin) / CLOCKS_PER_SEC;
cerr << "runtime: " << duration << "\n" << endl;
db->closedatabase(OPTIMISTIC);
delete db;
printf("Done\n");
return EXIT_SUCCESS;
}
Like all data structures in standard library, methods of vector are reentrant, but not thread-safe. That means different instances can be accessed by multiple threads independently, but each instance may only be accessed by one thread at a time and you have to ensure it. But since you have separate vector for each thread, that's not your problem.
What is probably your problem is the printf. printf is thread-safe, meaning you can call it from any number of threads at the same time, but at the cost of being wrapped in mutual exclusion internally.
Majority of work in the threaded part of your program is done inside printf. So what probably happens is that all the threads are started and quickly get to the printf, where all but the first will stop. When the printf finishes and releases the mutex, system considers scheduling the threads that were waiting for it. It probably does, so rather slow context switch happens. And repeats after every printf.
How exactly it happens depends on which actual locking primitive is being used, which depends on your operating system and standard library versions. The system should each time wake up only the next sleeper, but many implementations actually wake up all of them. So in addition to the printfs being executed in mostly round-robin fashion, incurring one context switch for each, there may be quite a few additional spurious wake-ups in which the thread just finds the lock is held and goes back to sleep.
So the lesson from this is that threads don't make things automagically faster. They only help when:
The thread spends most of it's time doing blocking system calls. In things like network servers the threads wait for data from the socket, than from data for response to come from disk and finally for network to accept the response. In such cases, having many threads helps as long as they are mostly independent.
There is just so many threads as there are CPU threads. Currently the usual number is 4 (either quad-core or dual-core with hyper-threading). More threads can't physically run in parallel, so they provide no gain and incur a bit of overhead. 16 threads is thus overkill.
And they never help when they all manipulate the same objects, so they end up spending most of the time waiting for locks anyway. In addition to any of your own objects that you lock, keep in mind that input and output file handles have to be internally locked as well.
Memory allocation also needs to internally synchronize between threads, but modern allocators have separate pools for threads to avoid much of it; if the default allocator proves to be too slow with many threads, there are some specialized ones you can use.

share pointer between processes via shared-memory IPC

I'm trying to share a pointer of defined class between the parent and the forked child through shared memory.
so in parent's main i create the pointer
mydata *p;
Reader::GetInstance()->Read(p, i+1);
pid = fork();
if (pid == -1){
cout << "error on fork"<<endl;
}else if (pid == 0){
cout << "i will fork now" <<endl;
const char * path = "./mydatamanager";
execl (path, "-", (char *)0);
break;
}else {
writer(shmid, p);
}
writer contains this
void writer(int shmid , mydata * p)
{
void *shmaddr;
shmaddr = shmat(shmid, (void *)0, 0);
if((int)shmaddr == -1)
{
perror("Error in attach in writer");
exit(-1);
}
else
{
memcpy( shmaddr, p, sizeof(*p) );
}
}
and my data is
class mydara {
public:
int var1;
int var2;
int var3;
int var4;
int var5;
int var6;
char *var7;
mydata (int v2, int v3,char *v7, int v6){
var2 = v2;
var3 = v3;
var7 =new char[128];
strcpy(var7, v7);
var6 = v6;
var4 = 0;
var5 = 0;
}
};
and in the mydatamanager i get this pointer this way
void reader(int shmid, mydata *& p)
{
cout << "in reader" << endl;
void *shmaddr;
//sleep(3);
shmaddr = shmat(shmid, (void *)0, SHM_RDONLY|0644);
if((int)shmaddr == -1)
{
perror("Error in reader");
exit(-1);
}
else
{
cout << "in else "<< endl;
p = (mydata*) shmaddr;
cout <<"shared memory address is " <<shmaddr <<endl;
cout <<"var5 "<< p->var5<< endl;
cout <<"var2 "<< p->var2<< " match with "<<getpid() << "?" << endl;
cout <<"var3 "<< p->var3<< endl;
cout <<"var4 "<< p->var4<< endl;
cout <<"var7 "<< p->var7<< endl; // the
//shmdt(shmaddr);
}
}
and mydatamanager main :
int main()
{
cout << "in main" <<endl;
int shmid;
shmid = shmget(IPC_PRIVATE, 4096, IPC_CREAT|0644);
cout << "in advanced point" <<endl;
sleep(1);
mydata * p;
reader (shmid, p);
cout << p->var7 <<endl;
return 0;
}
the results are always 0.
how can i share this pointer through the parent and the child and where is the fault in my code?
Hi i had a IPC task some weeks ago and finally decided to use boost.
http://blog.wolfgang-vogl.com/?p=528
http://www.boost.org/doc/libs/1_36_0/doc/html/interprocess/synchronization_mechanisms.html#interprocess.synchronization_mechanisms.semaphores.semaphores_interprocess_semaphores
First of all, you are not synchronising anything. So how do you know which runs first, the reader or the writer. Memory is bound to be zero in a newly allocated block, so hence you get zero as a result.
Any shared memory must ensure that the reader doesn't read until the writer has completed (at least part of) the writing process, at the very least.
Beware of sharing classes - you must not use virtual functions, as that will almost certainly do something ohterthan what you expect (crash, most likely, but other options are available, none of them particularly pleasant)
The simplest way to handle your problem is to create a semaphore in the parent process before the fork, have the child process try to acquire it before the read (instead of doing a sleep) and the parent process release it after the write.
First, here's functions to create, destroy, and retreive the id of the semaphore:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int create_semaphore(const char *path, char id, int count){
key_t k = ftok(path, id);
semid = semget(k, 1, IPC_CREAT | IPC_EXCL | 0600);
semctl(semid, 0, SET_VAL, count);
return semid;
}
int destroy_semaphore(int semid){
semctl(semid, 0, IPC_RMID, 0);
}
int get_semaphore(const char *path, char id){
key_t k = ftok(path, id);
semid = semget(k, 1, 0600);
return semid;
}
Now we need a function to acquire it, and another one to release it:
void acquire_semaphore(int semid){
sembuf op;
op.sem_num = O;
op.sem_op = -1;
op.sem_flg = 0;
semop(semid,&op,1);
}
void release_semaphore(int semid){
sembuf op;
op.sem_num = 0;
op.sem_op = 1;
op.sem_flg = 0;
semop(semid,&op,1);
}
With these boilerplate functions in place, you should be able to synchronize your processes.
So, you will need to provide a path and a unique id (in the form of a simple character) to create and identify your semaphore. If you already used ftok to create your shared memory id (shmid), you should understand the idea. Otherwise, just make sure that both values are the same within both processes.
In your writer code, put the following line:
semid = create_semaphore(argv[0], 'S', 0);
right before the pid = fork(); line, to create and acquire the semaphore at the same time.
Add the line:
release_semaphore(semid);
after the writer(shmid, mydata); instruction to release the semaphore. You will also need to declare semid somewhere in scope. I used the writer program path to create the semaphore, which is good practice to ensure that no other process has already used our path. The only catch is that you need to make sure that reader will use that same path. You can hardcode that value somewhere in reader's code, or better yet, pass it from writer in the execl parameters (left as an exercise).
Assuming that path is known in reader, all is left to do is to acquire the the semaphore likeso:
semid = get_semaphore(path, 'S');
acquire_semaphore(semid);
destroy_semaphore(semid);
before the line reader(shmid, mydata); in the main function of reader.
As other posts have said, sharing class instances through a shared memory segment is usually a very bad idea. It is much safer
to pass simple struct data, and reconstruct your object on the reader side (look up serialization and marshalling on the net for more information).
Ask if you have problems with this (untested) code.
Merry Christmas!

Problem with perror() calls affecting semaphore locking/unlocking

I'm trying to do some basic parallel processing to do an odd-even sort on integers using POSIX shared memory segments and unnamed semaphores. I have pretty much everything working at this point, except for one final thing: if I do not perror() directly after semaphore locks/unlocks the code acts differently (and subsequently sorts incorrectly). If I leave the perror() calls directly after semaphore locks and unlocks in, the code sorts the array of integers perfectly.
int semaphoreCheck = sem_init(&(sharedData->swapSem), 1, 1);
if (semaphoreCheck == -1)
{
perror( "failed to initialize semaphore" );
exit(EXIT_FAILURE);
}
pid_t fork1;
fork1 = fork();
if (fork1 == 0)
{
// original.child
pid_t fork2;
fork2 = fork();
if (fork2 == 0)
{
// child.child
// do a portion of the sort here
while(sharedData->evenSwap || sharedData->oddSwap)
{
// obtain lock on the shared vector
// int commandCheck = shmctl(sharedID, SHM_LOCK, NULL);
int commandCheck = sem_wait(&(sharedData->swapSem));
perror("semaphore lock");
// if lock was obtained
if (commandCheck == 0)
{
sharedData->evenSwap = false;
for( int index = 1; index < arraySize - 1; index +=2)
{
if( sharedData->vecData[index] > sharedData->vecData[index + 1] )
{
int temp;
temp = sharedData->vecData[index];
sharedData->vecData[index] = sharedData->vecData[index+1];
sharedData->vecData[index+1] = temp;
sharedData->evenSwap = true;
}
}
// release lock on the shared vector
commandCheck = sem_post(&(sharedData->swapSem));
perror("semaphore unlock");
if (commandCheck == -1)
{
perror("failed to unlock shared semaphore");
}
}
else perror("failed to lock shared semaphore");
}
_exit(0);
}
else if (fork2 > 0)
{
// child.parent
// do a portion of the sort here
while(sharedData->evenSwap || sharedData->oddSwap)
{
// obtain lock on the shared vector
int commandCheck = sem_wait(&(sharedData->swapSem));
perror("semaphore lock");
// if lock was obtained
if (commandCheck == 0)
{
sharedData->oddSwap = false;
for( int index = 0; index < arraySize - 1; index +=2)
{
if( sharedData->vecData[index] > sharedData->vecData[index + 1] )
{
int temp;
temp = sharedData->vecData[index];
sharedData->vecData[index] = sharedData->vecData[index+1];
sharedData->vecData[index+1] = temp;
sharedData->oddSwap = true;
}
}
// release lock on the shared vector
commandCheck = sem_post(&(sharedData->swapSem));
perror("semaphore unlock");
if (commandCheck == -1)
{
perror("failed to unlock shared semaphore");
}
}
else perror("failed to lock shared semaphore");
}
_exit(0);
}
else
{
// child.error
// forking error.
perror("failed to fork in child");
exit(EXIT_FAILURE);
}
}
else if( fork1 > 0)
{
// original.parent
// wait for the child process to finish.
waitpid(fork1, NULL, 0);
}
else
{
// forking error
perror("failed to fork");
exit(EXIT_FAILURE);
}
I can only guess that this has to do with how the semaphore blocks the process if a wait cannot be fulfilled, but I do not understand how perror() calls fix it.
I think your problem may be related to the way you are (not) checking that the conditions still apply after you get the semaphore, or that the checking conditions are themselves wrong.
You have:
while(sharedData->evenSwap || sharedData->oddSwap)
{
// obtain lock on the shared vector
int commandCheck = sem_wait(&(sharedData->swapSem));
perror("semaphore lock");
// if lock was obtained
if (commandCheck == 0)
{
sharedData->oddSwap = false;
After you get the semaphore, you should probably validate that either sharedData->evenSwap or sharedData->oddSwap is still true, relinquishing the semaphore if not. This is a standard idiom; you check, lock and recheck, because the status may have changed between the original check and the time you gain the lock.
Under this hypothesis, the perror() calls alter the timing of the processes, allowing the conditions to stay unchanged for longer than when the perror() calls are not present. So, there is a timing problem here, somewhere.