Slim Reader/Writer locks deadlocking - c++

For some reason a simple Slim Reader/Writer lock pattern is deadlocking and I don't know why. I have two threads who share the lock (one using exclusive ownership and one using shared ownership).
For some reason, when a second exclusive locking thread is introduced it gets extremely slow for no discernable reason.
my code:
#include <iostream>
#include <Windows.h>
SRWLOCK mainLock;
char myglobalvar;
int main()
{
myglobalvar = '0';
HANDLE t1 = CreateThread(nullptr, 0, [](void*)->DWORD
{
while (true)
{
AcquireSRWLockShared(&mainLock);
myglobalvar = 'S';
Sleep(500);
ReleaseSRWLockShared(&mainLock);
}
return 0;
}, nullptr, 0, nullptr);
Sleep(50);
HANDLE t2 = CreateThread(nullptr, 0, [](void*)->DWORD
{
while (true)
{
AcquireSRWLockExclusive(&mainLock);
myglobalvar = 'E';
ReleaseSRWLockExclusive(&mainLock);
}
return 0;
}, nullptr, 0, nullptr);
for (int i = 0; i < 100; i++)
{
AcquireSRWLockExclusive(&mainLock);
std::cout << myglobalvar << '\n';
ReleaseSRWLockExclusive(&mainLock);
Sleep(50);
}
}

Slim Reader/Writer (SRW) Locks
Shared mode, which grants shared read-only access to multiple reader
threads, which enables them to read data from the shared resource
concurrently.
As #Peter said, there is no chance for main thread to acquire the lock because of t2. And your slow feeling is the only std::cout.
The following code works fine for me.
#include <iostream>
#include <Windows.h>
SRWLOCK mainLock;
char myglobalvar;
int main()
{
myglobalvar = '0';
HANDLE t1 = CreateThread(nullptr, 0, [](void*)->DWORD
{
while (true)
{
AcquireSRWLockShared(&mainLock);
//myglobalvar = 'S';
std::cout << myglobalvar << '\n';
ReleaseSRWLockShared(&mainLock);
Sleep(250);
}
return 0;
}, nullptr, 0, nullptr);
Sleep(50);
HANDLE t2 = CreateThread(nullptr, 0, [](void*)->DWORD
{
while (true)
{
AcquireSRWLockExclusive(&mainLock);
myglobalvar = 'E';
ReleaseSRWLockExclusive(&mainLock);
Sleep(500);
}
return 0;
}, nullptr, 0, nullptr);
for (int i = 0; i < 100; i++)
{
AcquireSRWLockExclusive(&mainLock);
myglobalvar = 'S';
//std::cout << myglobalvar << '\n';
ReleaseSRWLockExclusive(&mainLock);
Sleep(500);
}
}

Related

Suspended thread time

I wanted to calculate the time between creating a thread and taking control by it, then compare this time for normal and suspended threads. But the results are quite chaotic.
So, my question is: Does it really makes sense? I think that instant resuming doesn't have a visible effect on the result.
#include <iostream>
#include <windows.h>
using namespace std;
DWORDLONG freq;
DWORD WINAPI startThread(LPVOID lpParam) {
DWORDLONG c_beg = *((DWORDLONG*)lpParam);
DWORDLONG c_end;
QueryPerformanceCounter((LARGE_INTEGER*)&c_end);
cout << (double(c_end - c_beg)) / (freq) << endl;
return 0;
}
int main()
{
DWORDLONG c_beg;
HANDLE iThread, sThread;
QueryPerformanceFrequency((LARGE_INTEGER*)&freq);
cout << "instant" << endl;
for (int i = 0; i < 10; i++) {
QueryPerformanceCounter((LARGE_INTEGER*)&c_beg);
iThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)startThread, (LPVOID*)&c_beg, 0, NULL);
WaitForSingleObject(iThread, INFINITE);
}
cout << "suspended" << endl;
for (int i = 0; i < 10; i++) {
QueryPerformanceCounter((LARGE_INTEGER*)&c_beg);
sThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)startThread, (LPVOID*)&c_beg, CREATE_SUSPENDED, NULL);
ResumeThread(sThread);
WaitForSingleObject(sThread, INFINITE);
}
CloseHandle(iThread);
CloseHandle(sThread);
return 0;
}

0xC0000005 Error on Multi Thread Animation

So I was making an application using C++ Console, with multi threading as below, then I got an error 0x0000005.
The first time it run it was working as usual. Can anyone help me with this problem?
I am using Code::Blocks IDE with Borland C++ 5.5, and I am planning to make this into Borland C++ 5.02
#include <windows.h>
#include <stdio.h>
#include <dos.h>
#include <iostream.h>
#include <conio.h>
void linesmov(int mseconds, int y);
void linesmov(int mseconds, int y)
{
int i=0;
while (true)
{
i=i+1;
// Or system("cls"); If you may...
gotoxy(i,y);
cout << "____||____||____";
gotoxy(i-1,y);
cout << " ";
Sleep(mseconds);
if (i>115)
{
i=0;
for(int o = 0; o < 100; o++)
{
gotoxy(0,y);
cout << " ";
}
}
}
}
DWORD WINAPI mythread1(LPVOID lpParameter)
{
printf("Thread inside %d \n", GetCurrentThreadId());
linesmov(5,10);
return 0;
}
DWORD WINAPI mythread2(LPVOID lpParameter)
{
printf("Thread inside %d \n", GetCurrentThreadId());
linesmov(30,15);
return 0;
}
int main(int argc, char* argv[])
{
HANDLE myhandle1;
DWORD mythreadid1;
HANDLE myhandle2;
DWORD mythreadid2;
myhandle1 = CreateThread(0,0,mythread1,0,0,&mythreadid1);
myhandle2 = CreateThread(0,0,mythread2,0,0,&mythreadid2);
printf("Thread after %d \n", mythreadid1);
getchar();
return 0;
}
All of these solutions in comments including mine are definitely not the way how it should be done. The main problem is lack of synchronization between threads and lack of processing their termination. Also, every function should be checked for thread-safe compatibility or should be wrapped to match it.
Considering std::cout since c++11 we have some data race guarantees:
Concurrent access to a synchronized (§27.5.3.4) standard iostream
object’s formatted and unformatted input (§27.7.2.1) and output
(§27.7.3.1) functions or a standard C stream by multiple threads shall
not result in a data race (§1.10). [ Note: Users must still
synchronize concurrent use of these objects and streams by multiple
threads if they wish to avoid interleaved characters. — end note ]
So lask of synchronization primitives is oblivious according to this note.
Considering processing of thread termination.
HANDLE threadH = CreateThread(...);
...
TerminateThread(threadH, 0); // Terminates a thread.
WaitForSingleObject(threadH, INFINITE); // Waits until the specified object is in the signaled state or the time-out interval elapses.
CloseHandle(threadH); // Closes an open object handle.
TerminateThread(), but be aware of this solution, because ..
WaitForSingleObject()
And this is only first steps to thread-safe way.
I would like to recommend C++ Concurrency in Action: Practical Multithreading by Anthony Williams for further reading.
Rude solution for synchronized output
#include <Windows.h>
#include <iostream>
#include <mutex>
std::mutex _mtx; // global mutex
bool online = true; // or condition_variable
void gotoxy(int x, int y)
{
COORD c = { x, y };
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), c);
}
void linesmov(int mseconds, int y) {
int i = 0;
while (online) {
i = i + 1;
// Or system("cls"); If you may...
_mtx.lock(); // <- sync here
gotoxy(i, y);
std::cout << "____||____||____"; gotoxy(i - 1, y);
std::cout << " ";
_mtx.unlock();
Sleep(mseconds);
if (i > 75)
{
i = 0;
for (int o = 0; o < 60; o++)
{
_mtx.lock(); // <- sync here
gotoxy(0, y);
std::cout << " ";
_mtx.unlock();
}
}
}
}
DWORD WINAPI mythread1(LPVOID lpParameter)
{
std::cout << "Thread 1" << GetCurrentThreadId() << std::endl;
linesmov(5, 10);
return 0;
}
DWORD WINAPI mythread2(LPVOID lpParameter)
{
std::cout << "Thread 2" << GetCurrentThreadId() << std::endl;
linesmov(30, 15);
return 0;
}
int main(int argc, char* argv[])
{
DWORD mythreadid1;
DWORD mythreadid2;
HANDLE myhandle1 = CreateThread(0, 0, mythread1, 0, 0, &mythreadid1);
HANDLE myhandle2 = CreateThread(0, 0, mythread2, 0, 0, &mythreadid2);
std::cout << "Base thread: " << GetCurrentThreadId() << std::endl;
getchar();
online = false;
WaitForSingleObject(myhandle1, INFINITE);
WaitForSingleObject(myhandle2, INFINITE);
CloseHandle(myhandle1);
CloseHandle(myhandle2);
return 0;
}
a) Both gotoxy not outputting via std::cout are not thread safe /synchronized. You need process-wide mutex to synchronize that
b) exception is likely due to fact that you do not use WaitForMultipleObjects in main to wait for threads to finish. Depending on hardware and optimization main may exit before threads finish their work.

How to make a thread contain a loop using mutexes to control flow?

I am working on a homework assignment and I've hit a wall. The assignment is to use 2 threads with controlling mutex to reduce a number x over a loop of 5 iterations.
The first thread does x=x-5
The second thread does x = x/5
The proper result should reduce x to 5 from 19530 over 5 iterations alternating between thread1 and thread2 at each iteration.
I have the following result as of now:
Thread1: x = 19525
Thread1: x = 19520
Thread1: x = 19515
Thread1: x = 19510
Thread1: x = 19505
From above it is clear that my second thread is not only not doing it's job but it's not doing anything at all.
Below is my code, it is written in C++ but the style is using the tools we learned in class which is how one would do it in C but it should work the same either way.
#include<iostream>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
using namespace std;
void *first(void *);
void *second(void *);
pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER;
int x = 19530; // global var that is being manipulated
int i = 1; // counter for the loops
int main() {
int t1, t2;
pthread_t thread1, thread2;
if((t1 = pthread_create( &thread1, NULL, first, NULL))) {
printf("Thread creation failed: %d\n", t2);
}
if((t2 = pthread_create( &thread2, NULL, second, NULL))) {
printf("Thread creation failed: %d\n", t2);
}
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
return(0);
}
void *first(void *){ // function for thread1
for(; i <=5; i++){
pthread_mutex_lock(&mymutex);
x = x-5;
cout << "Thread1: x = " << x << endl;
pthread_mutex_unlock(&mymutex);
}
}
void *second(void *){ // function for thread2
for(; i<=5; i++){
pthread_mutex_lock(&mymutex);
x = x/5;
cout << "Thread2: x = " << x << endl;
pthread_mutex_unlock(&mymutex);
}
}
Note I am brand new to the concept threads and mutex. And I'd prefer to stick to the way I have learned in class which I believe is called "the C way".
Thanks to rclgdr who commented above I was able to answer my own question the following code works as intended:
#include<iostream>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
using namespace std;
void *first(void *);
void *second(void *);
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; // thread1 mutex
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER; // thread2 mutex
int x = 19530; // global var that is being manipulated
int main() {
cout << "x = " << x << endl << endl;
int t1, t2;
pthread_t thread1, thread2;
pthread_mutex_lock(&mutex2);
if((t1 = pthread_create( &thread1, NULL, first, NULL))) {
printf("Thread creation failed: %d\n", t2);
}
if((t2 = pthread_create( &thread2, NULL, second, NULL))) {
printf("Thread creation failed: %d\n", t2);
}
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
return(0);
}
void *first(void *){ // function for thread1
for(int i = 1; i <=5; i++){
pthread_mutex_lock(&mutex1);
x = x-5;
cout << "Iteration " << i <<endl;
cout << "Thread1: x = " << x << endl;
pthread_mutex_unlock(&mutex2);
}
}
void *second(void *){ // function for thread2
for(int i = 1; i<=5; i++){
pthread_mutex_lock(&mutex2);
x = x/5;
cout << "Thread2: x = " << x << endl << endl;
pthread_mutex_unlock(&mutex1);
}
}

sem_timedwait with CLOCK_MONOTONIC_RAW/CLOCK_MONOTONIC

The example code for int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); uses CLOCK_REALTIME as the time source from clock_gettime(struct timespec *timeout) but this is susceptible to system clock time changes for example some other process changing time backwards.
Is there a support for sem_timedwait to support CLOCK_MONOTONIC time source
below is some example code for reference.
struct timespec ts;
sem_t sema;
sem_init(&sema, 0, 0)
int ret;
if ( -1 != (ret = clock_gettime(CLOCK_REALTIME, &ts))){
ts.tv_sec += 1;
return sem_timedwait(sema, &ts);
}
Is there a support for sem_timedwait to support CLOCK_MONOTONIC time source
Short answer: no.
But you could implement one if you're not using a 3rd party library or C++11 and don't need cross-platform compatibility:
#include <cstring> // memset
#include <ctime> // DEV_NOTE: some systems might need -lrt
#include <csignal> // DEV_NOTE: csignal contains a reference to CLOCK_MONOTONIC
#include <semaphore.h>
#if !defined(CLOCK_MONOTONIC)
#error CLOCK_MONOTONIC is not defined
#endif
typedef struct timespec tick_t;
static tick_t monotonic_tick()
{
tick_t tmp;
if (clock_gettime(CLOCK_MONOTONIC, &tmp) != 0) {
std::memset(&tmp, 0, sizeof(tick_t));
// error, throw std::exception(std::strerror(errno))
}
return tmp;
}
static double elapsed_us(tick_t init, tick_t end)
{
return ((end.tv_sec - init.tv_sec) * 1000000) + (static_cast<double>((end.tv_nsec - init.tv_nsec)) / 1000);
}
static double elapsed_ms(tick_t init)
{
return (elapsed_us(init, monotonic_tick()) / 1000);
}
static int sem_timed_wait(sem_t& sem, unsigned long timeout_ms)
{
if (timeout_ms == 0) {
if (sem_trywait(&sem) == 0) {
return 0;
}
} else {
tick_t start = monotonic_tick();
do {
if (sem_trywait(&sem) == 0) {
return 0;
}
} while (elapsed_ms(start) <= timeout_ms);
}
return -1;
}
Then to use it:
#include <iostream>
#include <pthread.h>
void* thread_fn(void* val)
{
sem_t* sem = static_cast<sem_t*>(val);
std::cout << std::endl << pthread_self() << " thread started" << std::endl;
if (sem_timed_wait(*sem, 1000) == 0) {
std::cout << std::endl << pthread_self() << " got it, sleeping 2 seconds..." << std::endl;
sleep(2); // sleep 2 seconds
std::cout << pthread_self() << " returning..." << std::endl;
// don't forget to release since we acquired the lock
sem_post(sem);
} else {
std::cout << pthread_self() << " timeout" << std::endl;
}
std::cout << pthread_self() << " thread returning" << std::endl;
return NULL;
}
int main(int argc, char* argv[])
{
sem_t sem;
pthread_t t1, t2;
sem_init(&sem, 0, 1); // binary semaphore
std::cout << "Creating..." << std::endl;
pthread_create(&t1, NULL, thread_fn, static_cast<void*>(&sem));
pthread_create(&t2, NULL, thread_fn, static_cast<void*>(&sem));
std::cout << "Joining..." << std::endl;
pthread_join(t1, NULL);
pthread_join(t2, NULL);
std::cout << "Leaving..." << std::endl;
return 0;
}
The above works on a wide array of *nix systems to include the BSD line. If you need a cross platform way of doing this, Windows and Apple have simpler mechanisms to do this.
Hope that can help.
Had the same problem with POSIX system,
Based on C++0x has no semaphores? How to synchronize threads?
and How do I deal with the system clock changing while waiting on a std::condition_variable? and Halûk Uçar answer
#include <stdio.h>
#include <thread>
#include <time.h>
#include <unistd.h>
#include <pthread.h>
class semaphore
{
private:
pthread_mutex_t m_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_condattr_t m_attr;
pthread_cond_t m_cond;
unsigned long count_ = 0;
public :
void init_sem()
{
int result = 0;
result = pthread_condattr_init(&m_attr);
result = pthread_condattr_setclock(&m_attr, CLOCK_MONOTONIC);
result = pthread_cond_init(&m_cond, &m_attr);
}
void notify() {
pthread_mutex_lock(&m_mutex);
++count_;
pthread_cond_signal(&m_cond);
pthread_mutex_unlock(&m_mutex);
}
void wait() {
pthread_mutex_lock(&m_mutex);
while (!count_) // Handle spurious wake-ups.
{
pthread_cond_wait(&m_cond, &m_mutex);
}
--count_;
pthread_mutex_unlock(&m_mutex);
}
void wait_for(int sec)
{
int rc = 0;
pthread_mutex_lock(&m_mutex);
if (!count_)
{
timespec tsTimeout;
clock_gettime(CLOCK_MONOTONIC, &tsTimeout);
// update time calculation to your specific case
tsTimeout.tv_sec += time;
// Handle spurious wake-ups.
while (!count_ && (rc == 0))
{
rc = pthread_cond_timedwait(&m_cond, &m_mutex, &tsTimeout);
}
}
if (rc == 0)
{
printf("success\n");
--count_;
}
else if (rc == ETIMEDOUT)
{
printf("timeout\n");
}
else
{
printf("error\n");
}
pthread_mutex_unlock(&m_mutex);
}
bool destroy()
{
return ((pthread_cond_destroy(&m_cond) == 0)
&& (pthread_mutex_destroy(&m_mutex) == 0)
&& (pthread_condattr_destroy(&m_attr)==0)) ? true : false;
}
};
You can implement your own semaphore routines by using
pthread_cond_signal() for sem_post()
pthread_cond_timedwait() for sem_timedwait()
pthread_cond_wait() for sem_wait()
pthread_cond_timedwait() at current time for sem_trywait()
Of course semaphore creation and deletion will include malloc and free, where you alloc a struct with all parameters (mutex, condition, ... ) needed for your semaphore implementation.
If anyone stumbles across this in the future:
glibc has now implemented this (since version 2.30):
https://www.gnu.org/software/libc/manual/html_node/Waiting-with-Explicit-Clocks.html
you can use sem_clockwait with CLOCK_MONOTONIC.

c++ pthreads opertions on a global char buffer

I am experimenting with pthreads. I am trying to create three threads and have them operate on a global char buffer. I am using mutex lock and unlock for their critical sections. The program flow should go: Main spawns three threads. Thread one locks, initializes the buffer, prints it out, signals thread two, and unlocks. Thread two enters its critical section operates on the buffer and signals thread three, etc. It seems to work, sometimes. Other times, it seems like it is getting suck in a spin lock. Any help in the right direction would be great. Thanks.
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <iostream>
using namespace std;
const int num_threads = 3;
char buffer[100];
pthread_mutex_t buffer_mutex = pthread_mutex_initializer;
pthread_cond_t buffer_cond = pthread_cond_initializer;
void* firstthreadfunc(void* proc) {
string a = "data received";
pthread_mutex_lock(&buffer_mutex);
sleep(1);
cout<<"threadone"<<endl;
for(int i = 0;i<14;i++){
buffer[i] = a[i];
cout<<buffer[i];
}
cout<<endl;
pthread_cond_signal(&buffer_cond);
pthread_mutex_unlock(&buffer_mutex);
return null;
}
void* secondthreadfunc(void* proc) {
string a = "data processed";
pthread_mutex_lock(&buffer_mutex);
pthread_cond_wait(&buffer_cond, &buffer_mutex);
sleep(1);
cout<<"threadtwo"<<endl;
for(int i = 0; i<15 ;i++){
buffer[i] = a[i];
cout<<buffer[i];
}
cout<<endl;
pthread_cond_signal(&buffer_cond);
pthread_mutex_unlock(&buffer_mutex);
return null;
}
void* thirdthreadfunc(void* proc) {
string a = "data sent";
pthread_mutex_lock(&buffer_mutex);
pthread_cond_wait(&buffer_cond, &buffer_mutex);
sleep(1);
cout<<"thread three"<<endl;
for(int i = 0;i<9;i++){
buffer[i] = a[i];
cout<<buffer[i];
}
cout<<endl;
pthread_cond_signal(&buffer_cond);
pthread_mutex_unlock(&buffer_mutex);
return null;
}
int main() {
pthread_t p_threadone, p_threadtwo, p_threadthree;;
pthread_attr_t attr;
pthread_attr_init(&attr);
for(int i = 0;i<100;i++){
buffer[i] = 'a';
}
//create threads
cout<<"creating threads"<<endl;
pthread_create(&p_threadone, &attr, firstthreadfunc, null);
pthread_create(&p_threadtwo, &attr, secondthreadfunc, null);
pthread_create(&p_threadthree, &attr, thirdthreadfunc, null);
//terminate threads
pthread_join(p_threadone,null);
pthread_join(p_threadtwo,null);
pthread_join(p_threadthree,null);
return 0;
}
Thanks WhozCraig and Tony, your answers resolved the issue. I understand what I was doing wrong.
First, where you're stuck. The following line in either thread2 or thread3 is the sticking point:
pthread_cond_wait(&buffer_cond, &buffer_mutex);
And by now you're asking, "Why?" Because your mistaking a condition variable as a state; not a signaling mechanism. Condition variables are intended to be used to signal interested waiters of change in state of something else: the predicate. You have none. Consider the following modified version of your code.
This uses two predicate values (I advise you stick with one per condvar until you become more comfortable with them; start simple), protecting them with the same mutex and signaling their change with the same condition variable. The important thing to note is that we don't wait on the condition variable until we know the predicate we're waiting for is not ready yet. And since we have the mutex locked, we can safely do check that predicate:
#include <iostream>
#include <string>
#include <unistd.h>
#include <pthread.h>
using namespace std;
const int NUM_THREADS = 3;
char buffer[100];
bool bDataReady = false;
bool bDataWaiting = false;
pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t buffer_cond = PTHREAD_COND_INITIALIZER;
void* firstThreadFunc(void* proc)
{
string a = "Data Received";
pthread_mutex_lock(&buffer_mutex);
cout<<"ThreadOne"<<endl;
std::copy(a.begin(), a.end(), buffer);
buffer[a.size()] = 0;
cout << buffer << endl;
bDataReady = true;
pthread_cond_broadcast(&buffer_cond);
pthread_mutex_unlock(&buffer_mutex);
return NULL;
}
void* secondThreadFunc(void* proc)
{
string a = "Data Processed";
pthread_mutex_lock(&buffer_mutex);
while (!bDataReady)
pthread_cond_wait(&buffer_cond, &buffer_mutex);
cout<<"ThreadTwo"<<endl;
std::copy(a.begin(), a.end(), buffer);
buffer[a.size()] = 0;
cout << buffer << endl;
bDataReady = false;
bDataWaiting = true;
pthread_cond_broadcast(&buffer_cond);
pthread_mutex_unlock(&buffer_mutex);
return NULL;
}
void* thirdThreadFunc(void* proc)
{
string a = "Data Sent";
pthread_mutex_lock(&buffer_mutex);
while (!bDataWaiting)
pthread_cond_wait(&buffer_cond, &buffer_mutex);
cout<<"Thread Three"<<endl;
std::copy(a.begin(), a.end(), buffer);
buffer[a.size()] = 0;
cout << buffer << endl;
bDataWaiting = false;
pthread_cond_broadcast(&buffer_cond);
pthread_mutex_unlock(&buffer_mutex);
return NULL;
}
int main() {
pthread_t p_threadOne, p_threadTwo, p_threadThree;;
pthread_attr_t attr;
pthread_attr_init(&attr);
for(int i = 0;i<100;i++){
buffer[i] = 'a';
}
//create Threads
cout<<"creating threads"<<endl;
pthread_create(&p_threadOne, &attr, firstThreadFunc, NULL);
pthread_create(&p_threadTwo, &attr, secondThreadFunc, NULL);
pthread_create(&p_threadThree, &attr, thirdThreadFunc, NULL);
//terminate Threads
pthread_join(p_threadOne,NULL);
pthread_join(p_threadTwo,NULL);
pthread_join(p_threadThree,NULL);
return 0;
}
Output
creating threads
ThreadOne
Data Received
ThreadTwo
Data Processed
Thread Three
Data Sent