1

I have an easy program for simulating readers-writers problem in C. User is asked to enter count of writers and count of readers. And then random number of writer - threads and reader - threads is created. The writing of items is simulated by global variable itemsCount - it represent thee ID of new inserted item (itemsCount + 1). I think that to this point the program works well.

But now i have to show writing conflict that is caused by bad written synchronization. I thought that is enough to simply remove the statement sem_wait(&w); or badly initilaizate the semaphore - for example sem_init(&w,0,5);

But it does nothing, i can't see any writing conflict. I thought, that in the output i will see something like:

Writer 1 writes item --> number of item: 1 | name of item: Writer 1

Writer 2 writes item --> number of item: 1 | name of item: Writer 2

(conflict: two items with one number). But none of this happens.

Where am i wrong?

Code with good synchronization:

#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>

#define MAX_READERS 10
#define MAX_WRITERS 10

sem_t w;    // semaphor for write access
sem_t m;    // mutex
int rc=0;   // readers count

int writersCount;   // how many writers does user want
int readersCount;   // how many readers does user want
pthread_t writersThread[MAX_WRITERS*5], readersThread[MAX_READERS*5];   // threads for writers and readers
int writeCount[MAX_WRITERS], readCount[MAX_READERS];    // how many times did each writer write and each reader read
int itemsCount=0;   // how many items is stored in the "DB"

void *writer(void *i)
{
    int a = *((int *) i);

    sem_wait(&w);   // P(w)
    printf("Writer %d writes item --> number of item: %d | name of item: Writer %d\n", a+1, ++itemsCount, a+1);
    writeCount[a]++;
    sem_post(&w);   // V(w)

    return 0;
}

void *reader(void *i)
{
    int a = *((int *) i);

    sem_wait(&m);   // P(m)
    rc++;
    if (rc == 1) {
        sem_wait(&w);   // P(w)
    }
    sem_post(&m);   // V (m)

    printf("Reader %d reads from DB.\n", a+1);
    readCount[a]++;

    sem_wait(&m);   // P(m)
    rc--;
    if (rc == 0) {
        sem_post(&w);   // V(w)
    }
    sem_post(&m);   // V(m)

    return 0;
}

int randomCount() // returns random integer between 1 and 5
{
    return 1 + 5.0 * rand() / RAND_MAX;
}

int main()
{
    srand(time(NULL));

    sem_init(&w,0,1);   // semaphore initialization
    sem_init(&m,0,1);

    int i;

    printf("Enter count of writers (max. %d):", MAX_WRITERS);
    scanf("%d",&writersCount);
    if (writersCount > MAX_WRITERS) {
        fprintf(stderr, "Max count of wirters is: %d\n", MAX_WRITERS);
        return 1;   
    }

    printf("Enter count of readers (max. %d):", MAX_READERS);
    scanf("%d",&readersCount);
    if (writersCount > MAX_READERS) {
        fprintf(stderr, "Max count of readers is %d\n", MAX_READERS);
        return 1;   
    }

    printf("---------------------------------------------\n");

    int readerIndexes[readersCount];    // reader indexes (will be passed to thread)
    int writerIndexes[readersCount];    // writer indexes (will be passed to thread)
    int totalReaders = 0;
    int totalWriters = 0;

    for (i=0; i<readersCount; i++)  // create readers (how many did user enter)
    {
        int j;
        int count;

        readerIndexes[i] = i;
        count = randomCount();
        for (j=0; j<count; j++) // let the reader read randomly from 1 to 5 times
        {
            pthread_create(&readersThread[totalReaders++], NULL, reader, &readerIndexes[i]);
        }
    }

    for (i = 0 ; i < writersCount ; i++)    // create writers (how many did user enter)
    {
        int j;
        int count;

        writerIndexes[i] = i;
        count = randomCount();
        for (j=0;j<count;j++)   // let the writer write randomly from 1 to 5 times
        {
            pthread_create(&writersThread[totalWriters++], NULL, writer, &writerIndexes[i]);
        }
    }

    for (i=0;i<totalWriters;i++) // join the threads
    {
        pthread_join(writersThread[i], NULL);
    }

    for (i=0;i<totalReaders;i++)
    {
        pthread_join(readersThread[i], NULL);
    }

    printf("---------------------------------------------\n");

    for (i=0;i<readersCount;i++)
    {
        printf("Reader %d read %d times\n", i+1, readCount[i]);
    }
    for (i=0;i<writersCount;i++)
    {
        printf("Writer %d wrote %d times\n", i+1, writeCount[i]);
    }

    sem_destroy(&w);
    sem_destroy(&m);
    return 0;
}

Output:

Enter count of writers (max. 10):5
Enter count of readers (max. 10):5
---------------------------------------------
Reader 1 reads from DB.
Reader 1 reads from DB.
Reader 2 reads from DB.
Reader 2 reads from DB.
Reader 2 reads from DB.
Reader 2 reads from DB.
Reader 3 reads from DB.
Reader 3 reads from DB.
Reader 4 reads from DB.
Reader 4 reads from DB.
Reader 4 reads from DB.
Writer 1 writes item --> number of item: 1 | name of item: Writer 1
Writer 1 writes item --> number of item: 2 | name of item: Writer 1
Writer 1 writes item --> number of item: 3 | name of item: Writer 1
Writer 2 writes item --> number of item: 4 | name of item: Writer 2
Writer 2 writes item --> number of item: 5 | name of item: Writer 2
Writer 2 writes item --> number of item: 6 | name of item: Writer 2
Writer 3 writes item --> number of item: 7 | name of item: Writer 3
Writer 3 writes item --> number of item: 8 | name of item: Writer 3
Writer 3 writes item --> number of item: 9 | name of item: Writer 3
Writer 3 writes item --> number of item: 10 | name of item: Writer 3
Writer 4 writes item --> number of item: 11 | name of item: Writer 4
Writer 5 writes item --> number of item: 12 | name of item: Writer 5
Writer 5 writes item --> number of item: 13 | name of item: Writer 5
Writer 5 writes item --> number of item: 14 | name of item: Writer 5
Reader 4 reads from DB.
Reader 4 reads from DB.
Reader 5 reads from DB.
Reader 5 reads from DB.
Reader 5 reads from DB.
Reader 5 reads from DB.
---------------------------------------------
Reader 1 read 2 times
Reader 2 read 4 times
Reader 3 read 2 times
Reader 4 read 5 times
Reader 5 read 4 times
Writer 1 wrote 3 times
Writer 2 wrote 3 times
Writer 3 wrote 4 times
Writer 4 wrote 1 times
Writer 5 wrote 3 times

Thank You very much

Pavel Straka
  • 427
  • 7
  • 19
  • Maybe i have the solution. It is necessary to write similar code to writer: usleep(rand() % 10); - pretends some action. But I am not sure why? Is it because that otherwise the thread would run too fast? – Pavel Straka Jan 15 '15 at 15:57
  • And the other thing is to load global variable into local variable of the thread, increment the local variable and then pass it again to global variable. This code of writer works perfectly: – Pavel Straka Jan 15 '15 at 16:12
  • `void *writer(void *i) { int a = *((int *) i); int myItemsCount; sem_wait(&w); // P(w) myItemsCount = itemsCount; myItemsCount++; usleep(rand() % 10); itemsCount = myItemsCount; printf("Writer %d writes item --> number of item: %d | name of item: Writer %d\n", a+1, myItemsCount, a+1); writeCount[a]++; sem_post(&w); // V(w) return 0; }` – Pavel Straka Jan 15 '15 at 16:12

1 Answers1

0

You have little chances to see OS scheduler interrupting your thread just a few instructions after wakeup (sem_wait) and one instruction before a memory barrier (sem_post).

The readers, as in can be seen from the log, just executed sequentially without unnecessary context switches (good job, scheduler!).

I think that if you rewrite your code like that:

int v = writeCount[a], cnt;
for (int cnt = 0; cnt < 1000; cnt++) {;} // some huge spinlock
writeCount[a] = v + 1;

- you'd be able to demonstrate a write conflict.

Yury Rumega
  • 220
  • 1
  • 12
  • Thank You, i get it. Am i right, that the use of usleep(rand() % 10) will have same effect? – Pavel Straka Jan 15 '15 at 16:00
  • *sem_post()* releases a semaphore, and is not (directly) related to memory barriers. [See this](http://stackoverflow.com/questions/10552085/difference-between-lock-memory-barrier-semaphore) – Déjà vu Jan 15 '15 at 16:19
  • ring0: it's implementation defined, but [Linux kernel documentation](https://www.kernel.org/doc/Documentation/memory-barriers.txt) says that releasing of a semaphore implies certain barriers on reordering made by CPU. Of course, this is not a memory barrier in the strictest sense, because the effect may be limited to one CPU (which caches the containing memory cell) – Yury Rumega Jan 15 '15 at 16:31
  • Pavel: usleep() is definitely not equal to a loop. POSIX states that "The usleep() function will cause the calling thread to be suspended from execution". Some code ever uses usleep(0) as a "thread yield" operation) This possibility makes the demonstration with usleep() less fair than one with busy loop. – Yury Rumega Jan 15 '15 at 16:50
  • OK, i understand. Dou You please have any idea why with usleep there is a writing conflict, but with loop there is not one? Thank You – Pavel Straka Jan 15 '15 at 16:58
  • EDIT: I had to increase the number of loops. For 1 000 000 loops i get writing conflict. – Pavel Straka Jan 15 '15 at 17:03