CIS 307: Measuring Time, Pthreads[Time], [Pthreads], [Locks for Pthreads]
Another problem is that "the current time" may mean a number of different things. It could be the standard Greenwich time. Or it could be the time in our time zone. Or it could be the time, measured in some unit (called a tick) since a standard initial time, called the epoch, usually set at 00:00:00 on 1 Jan 1970 at Greenwich.
We can use the function time to determine the time it takes to execute a command or program. But the precision of this command is low, depending on the version, tenths or hundredths of a second. We can request the system to suspend the execution of a program with the sleep command. Infortunately the interval can only be specified in seconds.
We will consider some data types and commands that we can use for dealing with time. I highly recommend that you use these tools to measure accurately and precisely the time it takes to execute various system services and some of your code.
Here are some useful functions (getclock and ctime_r) and data type (timespec) available in Unix:
#include <sys/timers.h>
int getclock(int clktyp, struct timespec *tp);
clktyp: it specifies the type of clock being used.
We use for it the value TIMEOFDAY
tp: A data structure where getclock will save the
current time.
struct timespec {
time_t tv_sec; /* number of seconds */
long tv_nsec;} /* number of nanoseconds */
int ctime_r(const time_t *timer, char *buffer, int len);
timer: number of seconds since epoch (1 jan 1970 at 00:00:00)
buffer: array where ctime_r will save the representation of
timer as a date string
len: size of buffer.
Be sure to compile programs containing ctime_r using the
library libc_r.a, i.e. use the compilation command modifier
-lc_r
We can use these functions to determine the time it takes to execute a
Unix command. This is done in the following program. Assuming that the
corresponding executable has been placed in the file timespec, one can
determine the time required to execute a shell command such as who
with the call
% timespec who
You may want to compare the values you get with timespec and who.
Here is the program:
/* timespec.c -- compile with "cc timespec.c -o timespec -lc_r"*/
#include <sys/types.h>
#include <sys/timers.h>
#include <stdlib.h>
#define TIMLEN 60
void timespec2string(struct timespec *ts, char buffer[]);
int main(char argc, char *argv[])
{
char buffer[TIMLEN] = "";
struct timespec tspec1, tspec2;
if (argc < 2) {
printf("Write: timespec shellcommand\n");
exit(1);}
getclock((timer_t)TIMEOFDAY, &tspec1;
if (system(argv[1]) != 0) {
perror("system");
exit(1);}
getclock((timer_t)TIMEOFDAY, &tspec2);
timespec2string(&tspec1, buffer, TIMLEN);
printf("Before: %s\n", buffer);
timespec2string(&tspec2, buffer, TIMLEN);
printf("After : %s\n", buffer);
}
void timespec2string(struct timespec *ts, char buffer[], int len)
/* It reads the time from ts and puts it in buffer (of size len) */
/* as a string */
{
ctime_r(&(ts->tv_sec), buffer, len);
/* ctime_r terminates the time with a carriage return. We eliminate it.*/
/* We report time in microseconds. That is the precision on our system.*/
sprintf(&buffer[24], " and %6d microseconds", (ts->tv_nsec)/1000);
}
Notice that we have called getclock before and after the fragment we
want to time, without printing out any information in between.
This is so as to get
a measure that does not include extraneous printing activities.
When doing measurements it is wise to do them a number of times and compute
average and standard deviation.
OSF/1 has a large number of functions for managing threads, using mutexes, using condition variables, etc. Check these services with the command
#include <pthread.h>
int pthread_create(
pthread_t *thread, /*The thread that is created */
pthread_attr_t attr, /*attributes for thread, pthread_attr_default*/
pthread_startroutine_t start_routine, /*function executed by thread*/
pthread_addr_t arg); /*address of argument passed to startroutine*/
/* Returns 0 iff successful */
#include <pthread.h>
int pthread_delay_np(
struct timespec *interval); /* delay thread for specified time */
where
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec;} /* nanoseconds */
#include <pthread.h>
And here is the example program. We run three concurrent threads, and this number can be easily changed. Later on in the course we will explore ways to synchronize threads. In the program we use rand_r instead of rand to generate random numbers because rand_r is re-entrant (i.e. thread-safe). Notice that each thread executes code that writes to different locations so as not to have race conditions. The exception is the variable shrd which is write-shared to demonstreate that concurrent threads actually share the same address space.
/* threadz.c -- compile with "cc threadz.c -o threadz -threads"*/
#include <sys/types.h>
#include <sys/timers.h>
#include <pthread.h>
#include <stdlib.h>
#define THREADSCOUNT 3
#define TOTALRUN 16
#define TMIN 1.0
#define TMAX 3.0
#define TIMLEN 60
int shrd;
struct state {
pthread_t t; /* A thread */
int who; /* It identifies a thread */
int seed; /* The seed used for random number generator*/
int val; /* Value returned by random number generator */
char buffer[TIMLEN]; /* String represnting current time */
} states[THREADSCOUNT];
void moo(struct state *s);
int main(void)
{
int i;
struct timespec maintime;
/* Initialize states */
states[0].seed = 1507; states[1].seed = 1511; states[2].seed = 1577;
for (i=0; i<THREADSCOUNT; i++) {
states[i].who = i;
if (pthread_create(&(states[i].t), pthread_attr_default,
(void *)moo, &(states[i])) != 0){
perror("pthread_create");
exit(1);}}
/* Wait a while then exit: all existing threads will die */
maintime.tv_sec = TOTALRUN;
maintime.tv_nsec =0;
pthread_delay_np(&maintime);
}
void getTime(struct timespec *ts, char buffer[], int len)
/* It places the current time in ts and puts in buffer (of length len) */
/* as a string the current time as a string */
{
getclock((timer_t)TIMEOFDAY, ts);
ctime_r(&(ts->tv_sec), buffer, len);
sprintf(&buffer[24], " and %d microseconds", (ts->tv_nsec)/1000);
}
void moo(struct state *s) {
/* We assume that a thread sleeps in each loop, from a minimum of */
/* TMIN to a maximum of TMAX, at random. */
struct timespec tspec;
struct timespec interval;
float r; /* random number in interval 0.0 .. 1.0 */
while (1){
getTime(&tspec, s->buffer, TIMLEN);
rand_r(&(s->seed), &(s->val));
r = (s->val)/((float)RAND_MAX);
shrd = s->who;
printf("Thread %d with shrd = %d sleeps %f at time %s\n", s->who,
shrd, TMIN+(r*(TMAX-TMIN)), s->buffer);
/* sleep for an a time between TMIN and TMAX */
interval.tv_sec = TMIN + (int)(r*(TMAX-TMIN));
interval.tv_nsec = (int)(r*1000000000);
pthread_delay_np(&interval);
getTime(&tspec, s->buffer, TIMLEN);
printf("Thread %d with shrd = %d after sleep at time %s\n",
s->who, shrd, s->buffer);}
}
If you run this program you will notice:
You can mark for deletion and reclaim the storage associated with a thread (of course, after it has terminated executing) with the command:
#include <pthread.h> int pthread_detach(pthread_t * thread);
This command will not terminate a thread that is executing, only indicating that we want to reclaim automatically its storage when it terminates execution.
We can use mutual exclusion semaphores, or locks, or mutexes with pthreads. These locks should be global to the threads.
#include <pthread.h>
int pthread_mutex_init(
pthread_mutex_t *mutex; /* The mutex being created */
pthread_mutexattr_t attr); /* usually pthread_mutexattr_default */
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
There are three kinds of mutexes depending on the value of the pthread_mutexattr_t attribute. We could have MUTEX_FAST_NP (the default), to be used in the standard lock..unlock protocol; MUTEX_RECURSIVE_NP: which allows one thread to do things like "lock .. lock .. unlock .. unlock"; MUTEX_NONRECURSIVE_NP is like the fast lock, but with better debugging facilities. One normally uses for the attribute the value pthread_mutexattr_default.
With pthreads are also available condition variables. They will make the creation of monitors very easy (as we will see). Of course, these monitors will operate only within a single Unix process.
Here is a program with threads that use locks to share a resource.
#include <sys/types.h>
#include <pthread.h>
pthread_t t1, t2;
pthread_mutex_t mutex;
struct { int x;
int y;} foo; /*This is a global data structure shared by threads*/
void moo(int * a);
int main(void)
{
int who[2] = {1,2};
/* Create a mutex */
if (pthread_mutex_init(&mutex, pthread_mutexattr_default)) {
perror("pthread_mutex_init");
exit(1);}
/* Create two threads */
if (pthread_create(&t1, pthread_attr_default, (void *)moo,&who[0]) != 0) {
perror("pthread_create");
exit(1);
}
if (pthread_create(&t2, pthread_attr_default, (void *)moo, &who[1]) != 0) {
perror("pthread_create");
exit(1);
}
/* Wait a while then exit: threads will die */
sleep(16);
}
void moo(int * a) {
while (1){
if (pthread_mutex_lock(&mutex)) {
perror("pthread_mutex_lock");
exit(1);}
printf("I am thread %d before sleep; x=%d, y=%d\n", *a, foo.x, foo.y);
foo.x = foo.y = *a;
sleep(*a);
printf("I am thread %d after sleep; x=%d, y=%d\n", *a, foo.x, foo.y);
if (pthread_mutex_unlock(&mutex)) {
perror("pthread_mutex_unlock");
exit(1);}
/* Here is a small delay to give the other thread a chance to run */
sleep(1);
}
}
ingargiola.cis.temple.edu