I must parallelize the following code with 2 locks and output should be in order:
Hello
World
Bye
But when I run the threads are randomly doing the work.
#include <omp.h>
#include <stdio.h>
int main()
{
int p;
omp_lock_t lock;
omp_init_lock(&lock);
#pragma omp parallel sections default(shared)
{
#pragma omp section
{
p = omp_get_thread_num();
omp_set_lock(&lock);
printf("Th%d: Hello\n",p);
omp_unset_lock(&lock);
}
#pragma omp section
{
p = omp_get_thread_num();
omp_set_lock(&lock);
printf("Th%d: World\n",p);
omp_unset_lock(&lock);
}
#pragma omp section
{
p = omp_get_thread_num();
printf("Th%d: Bye\n",p);
}
}
omp_destroy_lock(&lock);
return 0;
}
From my top comments:
Your lock does not guarantee ordering of your threads--only atomic access to stdout
.
So, for the latter, (e.g.) you'll always get "whole line" output:
Hello\n
World\n
Bye\n
And, not:
HeWoByllo\ne\nrld\n
But, with that you can get:
World\n
Bye\n
Hello\n
Also, does default(shared)
mean that p
is shared? I think p
needs to be private
[or needs to be set inside the critical section].
Further, the 3rd section doesn't do any locking.
I just ran the program, and got Th7
on all messages, so [AFAICT], p
does need to be private.
One way to solve this is with a "ticket lock" [*].
Edit: After rereading your question, the emphasis on two locks may indicate an alternate solution, which I added in the UPDATE below.
Because we can't predict what p
will be, each section needs a sequence number.
For example, Hello
needs 1, World
needs 2, and Bye
needs 3.
We need to add a shared variable that specifies which section may go next. Each section must loop on that variable [under lock] until it matches its assigned order/sequence number and increment it on the way out to allow the thread that is next in the sequence to run.
[*] A "ticket lock" is modelled on (e.g.) a bakery where you "take a number" when entering and buy stuff when the sign says: "Now serving ..." See: https://en.wikipedia.org/wiki/Ticket_lock
In general, one of the nice things about a ticket lock is that it guarantees progress and balanced access. In practice, the number of retries is relatively small/infrequent. This is particularly true if it's implemented using stdatomic.h
primitives.
Anyway, here's what I came up with:
#include <omp.h>
#include <stdio.h>
int main()
{
int p;
int owner = 1;
int more = 1;
omp_lock_t lock;
omp_init_lock(&lock);
#pragma omp parallel sections default(shared) private(p) private(more)
{
#pragma omp section
{
more = 1;
while (more) {
p = omp_get_thread_num();
omp_set_lock(&lock);
if (owner == 1) {
printf("Th%d: Hello\n",p);
more = 0;
owner += 1;
}
omp_unset_lock(&lock);
}
}
#pragma omp section
{
more = 1;
while (more) {
p = omp_get_thread_num();
omp_set_lock(&lock);
if (owner == 2) {
printf("Th%d: World\n",p);
more = 0;
owner += 1;
}
omp_unset_lock(&lock);
}
}
#pragma omp section
{
more = 1;
while (more) {
p = omp_get_thread_num();
omp_set_lock(&lock);
if (owner == 3) {
printf("Th%d: Bye\n",p);
more = 0;
owner += 1;
}
omp_unset_lock(&lock);
}
}
}
omp_destroy_lock(&lock);
return 0;
}
Here's the program output. The p
values can vary from run to run, but now, the ordering is always the same:
Th1: Hello
Th7: World
Th4: Bye
UPDATE:
The above solution is general [and I've used it a number of times in real production code], but it may not fit the requirement that you use two locks.
What I can think of is to have two locks that are initialized and locked by the main thread before entering any critical sections.
The first section (Hello
) needs no lock and just goes first. The other two sections (World
and Bye
) will first lock on lock2
and lock3
respectively.
When Hello
finishes, it unlocks lock2
.
This allows World
to get the lock and run. When finished, it unlocks both lock2
and lock3
.
The unlock of lock3
allows Bye
to get that lock. It prints and then unlocks lock3
Here's what I came up with for that:
#include <omp.h>
#include <stdio.h>
int main()
{
int p;
omp_lock_t lock2;
omp_init_lock(&lock2);
omp_set_lock(&lock2);
omp_lock_t lock3;
omp_init_lock(&lock3);
omp_set_lock(&lock3);
#pragma omp parallel sections default(shared) private(p)
{
#pragma omp section
{
p = omp_get_thread_num();
printf("Th%d: Hello\n",p);
omp_unset_lock(&lock2);
}
#pragma omp section
{
p = omp_get_thread_num();
omp_set_lock(&lock2);
printf("Th%d: World\n",p);
omp_unset_lock(&lock2);
omp_unset_lock(&lock3);
}
#pragma omp section
{
p = omp_get_thread_num();
omp_set_lock(&lock3);
printf("Th%d: Bye\n",p);
omp_unset_lock(&lock3);
}
}
omp_destroy_lock(&lock2);
omp_destroy_lock(&lock3);
return 0;
}