I need to control the frequency at which main
processes data. In the example, it just increases the value of a variable. I cannot use sleep
inside of main
because I need the frequency to be constant (and I don't know exactly how long does it take to process all the data). I just know for a fact that whatever processing I need to do takes less than 2 seconds, so I just need to prevent main
from increasing x
more than once every two seconds.
The solution I've found involves using two mutexes: locking one in main
and unlocking it in an extra
thread, and locking the other in extra
and unlocking it in main
. This extra
thread sleeps for 2 seconds per cycle.
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void *extra(void *arg)
{
pthread_mutex_t *lock = (pthread_mutex_t *) arg;
while(1) {
pthread_mutex_unlock(&lock[0]);
pthread_mutex_lock(&lock[1]);
sleep(2);
}
}
int main()
{
int x = 0;
pthread_mutex_t lock[2];
pthread_mutex_init(&lock[0], NULL);
pthread_mutex_init(&lock[1], NULL);
pthread_mutex_lock(&lock[1]);
pthread_t extra_thread;
pthread_create(&extra_thread, NULL, &extra, lock);
while(1) {
x += 1;
printf("%d\n", x);
pthread_mutex_lock(&lock[0]);
pthread_mutex_unlock(&lock[1]);
}
}
The reason why this works is that main
cannot lock lock[0]
twice; it has to wait until extra
unlocks it. However, according to The Open Group
Attempting to relock the mutex causes deadlock. If a thread attempts to unlock a mutex that it has not locked or a mutex which is unlocked, undefined behavior results.
Based on this, I see two issues here:
main
tries to lock lock[0]
twice it should deadlock. extra
unlocking lock[0]
, which was locked by main
, should be undefined behavior. Is my analysis correct?
Answering your questions,
- If
main
tries to locklock[0]
twice it should deadlock.
Yes, it would. Unless you use recursive mutexes, but then your child thread would never be able to lock the mutex as main
would always have it locked.
extra
unlockinglock[0]
, which was locked by main, should be undefined behavior.
Per the POSIX documentation for pthread_mutex_unlock()
, this is undefined behavior for a NORMAL
and non-robust mutex. However, the DEFAULT
mutex does not have to be NORMAL
and non-robust so there is this caveat:
If the mutex type is
PTHREAD_MUTEX_DEFAULT
, the behavior ofpthread_mutex_lock()
[andpthread_mutex_unlock()
] may correspond to one of the three other standard mutex types as described in the table above. If it does not correspond to one of those three, the behavior is undefined for the cases marked.
(Note my addition of pthread_mutex_unlock()
. The table of mutex behavior clearly shows that unlock behavior for a non-owner varies between different types of mutexes and even uses the same "dagger" mark in the "Unlock When Not Owner" column as used in the "Relock" column, and the "dagger" mark refers to the footnote I quoted.)
A robust NORMAL
, ERRORCHECK
, or RECURSIVE
mutex will return an error if a non-owning thread attempts to unlock it, and the mutex remains locked.
A simpler solution is to use a pair of semaphores (the following code is deliberately missing error checking along with empty lines that would otherwise increase readability in order to eliminate/reduce any vertical scroll bar):
#include <semaphore.h>
#include <pthread.h>
#include <stdio.h>
sem_t main_sem;
sem_t child_sem;
void *child( void *arg )
{
for ( ;; )
{
sem_wait( &child_sem );
sleep( 2 );
sem_post( &main_sem );
}
return( NULL );
}
int main( int argc, char **argv )
{
pthread_t child_tid;
sem_init( &main_sem, 0, 0 );
sem_init( &child_sem, 0, 0 );
pthread_create( &child_tid, NULL, child, NULL );
int x = 0;
for ( ;; )
{
// tell the child thread to go
sem_post( &child_sem );
// wait for the child thread to finish one iteration
sem_wait( &main_sem );
x++;
printf("%d\n", x);
}
pthread_join( child_tid, NULL );
}
The sane thread-safe solution is a condition variable:
//main thread
while(1) {
x += 1;
printf("%d\n", x);
pthread_mutex_lock(&lock);
pthread_cond_wait(&cond, &lock);
pthread_mutex_unlock(&lock);
}
then in the sleeper thread you do:
//sleeper thread
while(1) {
pthread_cond_signal(&cond);
sleep(2);
}
However you can also read the current time from the operating system and the sleep for the remaining time until next epoch using the high resolution sleep and time.
The next option is using a timerfd to wake you up in a fixed interval. And it can let you know if you missed a wake up.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.