c – How should functions that keep state across multiple invocations be made safe?

In Computer Systems: a Programmer’s Perspective,

12.7.1 Thread Safety

When we program with threads, we must be careful to write functions
that have a property called thread safety. A function is said to be
thread-safe if and only if it will always produce correct results when
called repeatedly from multiple concurrent threads. If a function is
not thread-safe, then we say it is thread-unsafe. We can identify four
(nondisjoint) classes of thread-unsafe functions:

Class 1: Functions that do not protect shared variables. We have
already encountered this problem with the thread function in Figure
12.16, which increments an unprotected global counter variable cnt. This class of thread-unsafe functions is relatively easy to make
thread-safe: protect the shared variables with synchronization
operations such as P and V . An advantage is that it does not require
any changes in the calling program. A disadvantage is that the
synchronization operations slow down the function.

/* WARNING: This code is buggy! */
#include "csapp.h"

void *thread(void *vargp); /* Thread routine prototype */

/* Global shared variable */
volatile long cnt = 0; /* Counter */

int main(int argc, char **argv)
{
  long niters;
  pthread_t tid1, tid2;

  /* Check input argument */
  if (argc != 2) {
    printf("usage: %s <niters>n", argv(0));
    exit(0);
  }
  niters = atoi(argv(1));

  /* Create threads and wait for them to finish */
  Pthread_create(&tid1, NULL, thread, &niters);
  Pthread_create(&tid2, NULL, thread, &niters);
  Pthread_join(tid1, NULL);
  Pthread_join(tid2, NULL);

  /* Check result */
  if (cnt != (2 * niters))
    printf("BOOM! cnt=%ldn", cnt);
  else
    printf("OK cnt=%ldn", cnt);
  exit(0);
}

/* Thread routine */
void *thread(void *vargp)
{
  long i, niters = *((long *)vargp);

  for (i = 0; i < niters; i++)
    cnt++;

  return NULL;
}

**Class 2: Functions that keep state across multiple invocations.**A
pseudorandom number generator is a simple example of this class of
thread-unsafe functions. Consider the pseudorandom number generator
package in Figure 12.37. The rand function is thread-unsafe because
the result of the current invocation depends on an intermediate result
from the previous iteration. When we call rand repeatedly from a
single thread after seeding it with a call to srand, we can expect a
repeatable sequence of numbers. However, this assumption no longer
holds if multiple threads are calling rand. The only way to make a
function such as rand thread-safe is to rewrite it so that it does not
use any static data, relying instead on the caller to pass the state
information in arguments. The disadvantage is that the programmer is
now forced to change the code in the calling routine as well. In a
large program where there are potentially hundreds of different call
sites, making such modiļ¬cations could be nontrivial and prone to
error.

unsigned next_seed = 1;

/* rand - return pseudorandom integer in the range 0..32767 */
unsigned rand(void)
{
  next_seed = next_seed*1103515245 + 12543;
  return (unsigned)(next_seed>>16) % 32768;
}

/* srand - set the initial seed for rand() */
void srand(unsigned new_seed)
{
  next_seed = new_seed;
}
  1. In the example in case 2, according to the solution in the quote, should the “rand()” function be changed to “unsigned rand(unsigned next_seed)”?

    /* rand - return pseudorandom integer in the range 0..32767 */
    unsigned rand(unsigned next_seed)
    {
      return (unsigned)(next_seed>>16) % 32768;
    }
    

    Then how should the new rand() be used to avoid problems?

  2. What are the differences between the problem of Class 2 and the
    problem of Class 1? (Very similar, aren’t they?)

Thanks.