[Operating System - Synchronization] Banker's Algorithm, multi-threaded atomic operations, POSIX mutex, semaphore to solve producer-consumer problems

Lab Week 14 Experiment Report

Experiment content: process synchronization

  • Compile and run the sample code of Lecture 17: alg.17-1-bankers-6; compile and run the sample code of Lecture 18: alg.18-1 ~ alg.18-5. Point out what you think is not appropriate and improve it.

  • Compile and run the sample code of Lecture 18: alg.18-6 ~ alg.18-8, discuss the producer-consumer problem, shared memory protection, process synchronization mechanism and improve it

I. Banker's Algorithm

The banker's algorithm is an algorithm used by the operating system to avoid deadlocks. It is modeled on the bank's loan control process, tentatively allocates system resources, and then conducts security checks to determine whether the allocation is safe (whether it will cause deadlocks). It is safe to deallocate and let the process continue to wait.

In the algorithm, it is mainly completed with six data structures, including three one-dimensional arrays and three two-dimensional arrays, which are used to give the current state of the system. It is advisable to use N to represent the current number of processes, and M to represent the resources that can be allocated type, definition:

int resource[M]int available[M]int request[M]int max[N][M]int allocation[N][M]int need[N][M]

  • resource[i]Indicates the type of the i-th allocatable resource

  • available[i]Indicates the available amount of the i-th resource

  • request[i]Indicates the amount of application for the i-th resource in this process

  • max[i][j]Indicates the maximum demand for resource j by process i

  • allocation[i][j]Indicates the amount of resource j obtained by process i

  • need[i][j]Indicates the amount of resources that process i still needs for resource j

When process P i P_iPiWhen applying for resources to the system, the following checks are required:

(1) If Request[i] <= Need[i], go to (2), otherwise report an error;

(2) If Request[i] <= Avaliablethen turn to (3), otherwise the process enters the waiting state;

(3) Then, assuming that the system allocates resources to the process and generates a new state, there are:

Avaliale = Avaliable - Request[i];
Allocation[i] = Allocation[i] + Request[i];
Need[i] = Need[i] - Request[i];

In addition, during the process of applying for a process, it is necessary to perform a security check to find a safe sequence. If the new state of the system is safe, the allocation is completed; otherwise, the original state needs to be restored, and the process enters a waiting state .

Security check steps:

Define data structures: int work[M]and bool finish[N]:

  • work[i]Indicates the current assumed availability of the i-th type resource ( work[i] = available[i] - request[i])
  • finish[j]Indicates whether the jth process has completed the security check

Pseudocode for checking steps:

(1) Data structure initialization

work[i] = avaliable[i]; // 让work为当前资源的可用量
finish[j] = false;  // finish初始化为false,表示未完成安全性检查

(2) Loop judgment

a. finish[i] == false; //
b. need[i] <= work;
//若都不满足,转(4)

(3) Recycling resources

work = work + allocation; //回收后的资源为可用资源+已分配资源
finish[i] = true; //已检查
//转(2)继续判断其它进程

(4) Final judgment

finish[i] == true //若对所有进程都有该条件成立,则系统处于安全状态
//否则处于不安全状态,因为资源分配给该进程后,没有一个进程能够完成并释放资源,最终将导致死锁。

Finally, a safe sequence is returned, such as {P0, P3, P2, P1}, indicating the current remaining resource work of the system, which is allocated to process P0 first, and then recycledwork+=allocation[p0]

It is redistributed to process P3, and then recycled work += allocation[p3], and so on, and finally all processes are satisfied.

Routine code:

resource_request function

int resource_request(int pro_i)
{
    
    
    int j, k, ret;
    
    for (k = 0;  k < M; k++) {
    
    
        if(request[k] > need[pro_i][k]) {
    
    
            printf("request[%d] > need[%d][%d]: request[%d] = %d, need[%d][%d] = %d\n", k, pro_i, k, k, request[k], pro_i, k, need[pro_i][k]);
            return EXIT_FAILURE;
        }
    }

    for (k = 0; k < M; k++) {
    
    
        if(request[k] > available[k]) {
    
    
            printf("request[%d] > available[%d]: request[%d] = %d, available[%d] = %d\n", k, k, k, request[k], k, available[k]);
            return EXIT_FAILURE;
        }
    }

    for (k = 0; k < M; k++) {
    
    
        work[k] = available[k] - request[k]; /* work[] as the pretending available[] */
    }

    ret = safecheck(pro_i); /* check whether the pretending state is safe */

    if(ret == EXIT_SUCCESS) {
    
     /* confirm the pretending state as the new state */
        for (k = 0; k < M; k++) {
    
    
            available[k] -= request[k];
            need[pro_i][k] -= request[k];
            allocation[pro_i][k] += request[k];
        }
    }

    return ret;
}

Code description:

This part of the code completes the judgment of the resource allocation request and calls the security detection function to judge whether the request is safe;

1. If the process's request for resources is greater than the process's demand for resources, an error will be returned;

2. If the process's request for a resource is greater than the available amount of the resource, an error is returned;

Among them, the work array is also initialized, work[k] = available[k] - request[k]and the remaining amount (avaliavle) of work is "probing" to be tested in the safety detection function. If the detection result is safe, the state is updated and the corresponding operation is performed:

available[k] -= request[k];
need[pro_i][k] -= request[k];
allocation[pro_i][k] += request[k];

safecheck function

int safecheck(int pro_i)
{
    
    
    int finish[N] = {
    
    FALSE};
    int safe_seq[N] = {
    
    0};
    int i, j, k, l;

    printf("\nsafecheck starting >>>>>>>>\n");

    for (i = 0; i < N; i++) {
    
     /* we need to select N processes, i is just a counter */
        for (j = 0; j < N; j++) {
    
     /* check the j-th process */
            if(finish[j] == FALSE) {
    
    
                if(j == pro_i) {
    
    
                    for (k = 0; k < M; k++) {
    
    
                        if(need[pro_i][k] - request[k] > work[k]) {
    
     /* if pretending need[pro_i] > work[] */
                            break; /* to check next process */
                        }
                    }
                } else {
    
    
                    for (k = 0; k < M; k++) {
    
    
                        if(need[j][k] > work[k]) {
    
    
                            break; /* to check next process */
                        }
                    }
                }

                if(k == M) {
    
     /* the j-th process can finish its task */
                    safe_seq[i] = j;
                    finish[j] = TRUE;
                    printf("safe_seq[%d] = %d\n", i, j);
                    printf("new work vector: ");
                    if(j == pro_i) {
    
    
                        for (l = 0; l < M; l++) {
    
     /* process pro_i releasing pretending allocated resources */
                            work[l] = work[l] + allocation[pro_i][l] + request[l];
                            printf("%d, ", work[l]);
                        }
                    } else {
    
    
                        for (l = 0; l < M; l++) {
    
     /* another process releasing allocated resource */
                            work[l] = work[l] + allocation[j][l];
                            printf("%d, ", work[l]);
                        }
                    }
                    printf("\n");

                    break; /* to select more process */
                }
            }
        }

        if(j == N) {
    
    
            break; /* not enough processes can pass the safecheck */
        }
    }

    if(i == N) {
    
     /* all N processes passed the safecheck */
        printf("A safty sequence is: ");
        for (j = 0; j < N; j++) {
    
    
            printf("P%d, ", safe_seq[j]);
        }
        printf("\n");
        return EXIT_SUCCESS;
    }
    else {
    
    
        printf("safecheck failed, process %d suspended\n", pro_i);
        return EXIT_FAILURE;
    }
}


Code description:

This part of the code completes the security check in the banker's algorithm;

At the beginning, the demand for resources of a certain process Pi has been input and stored in the array request[M], and there is also work[M]an array;

Next, it is to check the process security of N, and analyze it part by part:

First, carry out loop judgment on N, if the process has not completed the security check, that is finish[j] == FALSE, perform the following detection:

if(finish[j] == FALSE) {
    
    
  if(j == pro_i) {
    
    
     for (k = 0; k < M; k++) {
    
    
           if(need[pro_i][k] - request[k] > work[k]) {
    
     /* if pretending need[pro_i] > work[] */
              break; /* to check next process */
                              //当前无法满足该进程需求
              }
     }else {
    
    
         for (k = 0; k < M; k++) {
    
    
             if(need[j][k] > work[k]) {
    
     //如果需求量大于剩余量
                              break; /* to check next process */
           }
     }
  }
     if(k == M) {
    
     /* the j-th process can finish its task */
       safe_seq[i] = j; // 加入安全序列中
       finish[j] = TRUE; // 已检查
       printf("safe_seq[%d] = %d\n", i, j);
       printf("new work vector: ");
       if(j == pro_i) {
    
    
         for (l = 0; l < M; l++) {
    
     /* process pro_i releasing pretending allocated resources */
           work[l] = work[l] + allocation[pro_i][l] + request[l];
           printf("%d, ", work[l]);
         }
       } else {
    
    
         for (l = 0; l < M; l++) {
    
     /* another process releasing allocated resource */
           work[l] = work[l] + allocation[j][l];
           printf("%d, ", work[l]);
         }
       }
       printf("\n");

       break; /* to select more process */
     }
    /*.......*/
}

Here, if the currently traversed process is a process that needs to obtain resources pro_i, it will be judged need[pro_i][k] - request[k] > work[k], which means that if the process's demand for resources - request amount > remaining resource amount (here is the amount of "detection"), then The current remaining amount of resources cannot complete the work required by the process , nor can it release the resources of the process. Similarly, if other processes need[j][k] > work[k]cannot complete the work of the process, then for these processes, they cannot pass the security check.

Conversely, if the process can complete its work, then it is safe, add it to the security sequence, and then perform resource recovery work[l] = work[l] + allocation[pro_i][l] + request[l](process pro_i) or work[l] = work[l] + allocation[j][l](other processes), update the work, and then perform subsequent processes Judgment as above.

If all processes can pass the security check, the corresponding security sequence will be output safe_seq;

operation result:

Initial state, now enter the process number to be operated (input 0).

image-20220515154449924

The first is the initial state. There are 3 types of resources preset in the current system. The number of available resources is avaliavle = 10, 5, 7. There are 5 processes Pro0 ~ Pro4 in total. The allocated resources and demand are given by allocation and need respectively. out.

Enter the number of types of resources required by process P0, which are 3, 3, and 3 respectively

image-20220515154857500

Output the safecheck process of 5 processes with the number of current remaining system resources as the work vector. Finally, the new state of the system is obtained, and the remaining resources are 7, 2, and 4 respectively.

The security sequence is P0, P1, P2, P3, P4

Now operate on the process P1 to obtain the amount of resources 3, 1, 2

image-20220515155318464

At this time, the work vector of safe check is the remaining system resources 7, 2, 4, and the safe sequence is P1, P0, P2, P3, P4

Operate process 4 to obtain system resources 4, 1, 3

image-20220515165700515

At this time, resource 3 is insufficient, the amount of remaining resources is 2, and the amount of requested resources is 3.

Operate process 1 to obtain system resources 1, 1, 1

image-20220515165846980

At this time, the resource request is greater than the process's demand for resources.

Operate process 1 to obtain system resources 0, 1, 0

image-20220515170047956

Successfully allocated resources to process 1.

II. Process Synchronization

Atomicity of Operations in Multithreaded Programs

Atomic and non-atomic operations:

1. Related functions

__sync__ atomic family

// 将value加到*ptr上,结果更新到*ptr,并返回操作之前*ptr的值
type __sync_fetch_and_add (type *ptr, type value); 

// 从*ptr减去value,结果更新到*ptr,并返回操作之前*ptr的值
type __sync_fetch_and_sub (type *ptr, type value, ...) 

// 将*ptr与value相或,结果更新到*ptr, 并返回操作之前*ptr的值
type __sync_fetch_and_or (type *ptr, type value, ...) 

// 将*ptr与value相与,结果更新到*ptr,并返回操作之前*ptr的值
type __sync_fetch_and_and (type *ptr, type value, ...) 

// 将*ptr与value异或,结果更新到*ptr,并返回操作之前*ptr的值
type __sync_fetch_and_xor (type *ptr, type value, ...) 

// 将*ptr取反后,与value相与,结果更新到*ptr,并返回操作之前*ptr的值
type __sync_fetch_and_nand (type *ptr, type value, ...) 

// 将value加到*ptr上,结果更新到*ptr,并返回操作之后新*ptr的值
type __sync_add_and_fetch (type *ptr, type value, ...) 

// 从*ptr减去value,结果更新到*ptr,并返回操作之后新*ptr的值
type __sync_sub_and_fetch (type *ptr, type value, ...) 

// 将*ptr与value相或, 结果更新到*ptr,并返回操作之后新*ptr的值
type __sync_or_and_fetch (type *ptr, type value, ...) 

// 将*ptr与value相与,结果更新到*ptr,并返回操作之后新*ptr的值
type __sync_and_and_fetch (type *ptr, type value, ...) 

// 将*ptr与value异或,结果更新到*ptr,并返回操作之后新*ptr的值
type __sync_xor_and_fetch (type *ptr, type value, ...)

// 将*ptr取反后,与value相与,结果更新到*ptr,并返回操作之后新*ptr的值
type __sync_nand_and_fetch (type *ptr, type value, ...) 

// 比较*ptr与oldval的值,如果两者相等,则将newval更新到*ptr并返回true
bool __sync_bool_compare_and_swap (type *ptr, type oldval type newval, ...)

// 比较*ptr与oldval的值,如果两者相等,则将newval更新到*ptr并返回操作之前*ptr的值
type __sync_val_compare_and_swap (type *ptr, type oldval type newval, ...) 

// 发出完整内存栅栏
__sync_synchronize (...) 

// 将value写入ptr,对ptr加锁,并返回操作之前ptr的值。
type __sync_lock_test_and_set (type ptr, type value, ...)

// 将0写入到ptr,并对*ptr解锁。
void __sync_lock_release (type ptr, ...)


2. The routine code runs:

Synchronized Atomic Operations

alg.18-1-syn-fetch-demo.c

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define MAX_N 40

static int count_1 = 0;
static int count_2 = 0;

void *thread_func1(void *arg)
{
    
    

    for (int i = 0; i < 20000; ++i) {
    
    
        __sync_fetch_and_add(&count_1, 1);
    }

    pthread_exit(NULL);
}

void *thread_func2(void *arg)
{
    
    
    for (int i = 0; i < 20000; ++i) {
    
    
        count_2++;
    }

    pthread_exit(NULL);
}

int main(void)
{
    
    
    pthread_t ptid_1[MAX_N];
    pthread_t ptid_2[MAX_N];

    for (int i = 0; i < MAX_N; ++i) {
    
    
        pthread_create(&ptid_1[i], NULL, &thread_func1, NULL);
    }

    for (int i = 0; i < MAX_N; ++i) {
    
    
        pthread_create(&ptid_2[i], NULL, &thread_func2, NULL);
    }

    for (int i = 0; i < MAX_N; ++i) {
    
    
        pthread_join(ptid_1[i], NULL);
    }

    for (int i = 0; i < MAX_N; ++i) {
    
    
        pthread_join(ptid_2[i], NULL);
    }

    printf("result conut_1 = %d\n", count_1);
    printf("result conut_2 = %d\n", count_2);

    return 0;
}


Code description:

In thread_func1()the function, execute 20,000 loops, and __sync_fetch_and_add(&count_1, 1)add 1 to count_1 each time the atomic operation is called,

In thread_func2()the function, only the count_2++ operation is performed, which is not an atomic operation. From the corresponding assembly code, ** this operation needs to be divided into 3 atomic steps, which are to read count_2 from the memory to the register , then add 1, and finally write the value back to the memory, **so in this process, conflicts between threads are prone to occur, which in turn produces erroneous results.

operation result

image-20220516111359234

Obviously, it can be seen from the results that in thread_func1()the function, the result of the operation performed by calling the atomic operation is correct, but in thread_func2()the function, the result obtained is wrong.

alg.18-1-syn-fetch.c

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int main(void)
{
    
    
    uint8_t i, val;
    
    i = 30; val = 10;
	printf("__sync_fetch_and_add(&i, val)\n");
	printf("old i = %d, val = %d, ", i, val);
	printf("ret = %d, ", __sync_fetch_and_add(&i, val));
    printf("new i = %d\n\n", i);
    
    i = 30; val = 10;
	printf("__sync_add_and_fetch(&i, val)\n");
	printf("old i = %d, val = %d, ", i, val);
	printf("ret = %d, ", __sync_add_and_fetch(&i, val));
    printf("new i = %d\n\n", i);

    i = 30; val = 10;
	printf("__sync_fetch_and_sub(&i, val)\n");
	printf("old i = %d, val = %d, ", i, val);
	printf("ret = %d, ", __sync_fetch_and_sub(&i, val));
    printf("new i = %d\n\n", i);
    
    i = 0x16; val = 0x2A;
	printf("__sync_fetch_and_or(&i, val)\n"); /* 00010110 | 00101010 */
    printf("old i = 0x%x, val = 0x%x, ", i, val);
	printf("ret = 0x%x, ", __sync_fetch_and_or(&i, val));
    printf("new i = 0x%x\n\n", i);
    
    i = 0x16; val = 0x2A;
	printf("__sync_fetch_and_and(&i, val)\n"); /* 00010110 & 00101010 */
    printf("old i = 0x%x, val = 0x%x, ", i, val);
	printf("ret = 0x%x, ", __sync_fetch_and_and(&i, val));
    printf("new i = 0x%x\n\n", i);
    
    i = 0x16; val = 0x2A;
	printf("__sync_fetch_and_xor(&i, val)\n"); /* 00010110 ^ 00101010 */
    printf("old i = 0x%x, val = 0x%x, ", i, val);
	printf("ret = 0x%x, ", __sync_fetch_and_xor(&i, val));
    printf("new i = 0x%x\n\n", i);
    
    i = 0x16; val = 0x2A;
	printf("__sync_fetch_and_nand(&i, val)\n"); /* ~(00010110 & 00101010) */
    printf("old i = 0x%x, val = 0x%x, ", i, val);
	printf("ret = 0x%x, ", __sync_fetch_and_nand(&i, val));
    printf("new i = 0x%x\n\n", i);
    
	return 0;
}



Code description:

In this part of the code, the atomic operation of the __sync__ family is called, and the calculation result is printed;

__sync_fetch_and_add(&i, val)What is achieved is to first take out the value of i, add the value corresponding to val, and finally return the value before i has not changed, but on the __sync_add_and_fetch(&i, val)contrary, first add the value corresponding to val to i, and finally return the value after i has changed.

operation resultimage-20220518124948283

image-20220516111540749

It can be seen __sync_fetch_and_add(&i, val)that after execution, the implementation is to first take out the value of i 30, add the value corresponding to val 10, and finally return the value 30 before i has not changed, and finally print out the value of i 40; and first put i __sync_add_and_fetch(&i, val)= Add 30 to the value 10 corresponding to val, and finally return the changed value of i to 40.

alg.18-2-syn-compare.c

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int main(void)
{
    
    
    uint8_t i, oldval, newval;
    
    i = 30; oldval = 30; newval = 40;
    printf("__sync_bool_compare_and_swap(&i, oldval, newval)\n");
    printf("i = %d, oldval = %d, newval = %d\n", i, oldval, newval);
    printf("ret = %d, ", __sync_bool_compare_and_swap(&i, oldval, newval));
    printf("new i = %d\n", i);
    i = 30; oldval = 10; newval = 40;
    printf("__sync_bool_compare_and_swap(&i, oldval, newval)\n");
    printf("i = %d, oldval = %d, newval = %d\n", i, oldval, newval);
    printf("ret = %d, ", __sync_bool_compare_and_swap(&i, oldval, newval));
    printf("new i = %d\n\n", i);
 
    i = 30; oldval = 30; newval = 40;
    printf("__sync_val_compare_and_swap(&i, oldval, newval)\n");
    printf("i = %d, oldval = %d, newval = %d\n", i, oldval, newval);
    printf("ret = %d, ", __sync_val_compare_and_swap(&i, oldval, newval));
    printf("new i = %d\n", i);
    i = 30; oldval = 10; newval = 40;
    printf("__sync_val_compare_and_swap(&i, oldval, newval)\n");
    printf("i = %d, oldval = %d, newval = %d\n", i, oldval, newval);
    printf("ret = %d, ", __sync_val_compare_and_swap(&i, oldval, newval));
    printf("new i = %d\n\n", i);

    i = 30; newval = 10;
    printf("__sync_lock_test_and_set(&i, newval)\n");
    printf("i = %d, newval = %d\n", i, newval);
    printf("ret = %d, ", __sync_lock_test_and_set(&i, newval));
    printf("new i = %d\n", i);

    i = 30;
    printf("__sync_lock_release(&i)\n");
    printf("i = %d\n", i);
    __sync_lock_release(&i); /* no return value */
    printf("new i = %d\n", i);

    return 0;
}


Code description:

__sync_bool_compare_and_swap(&i, oldval, newval)For atomic comparison and exchange, if the value of i is the same as that of oldval, then assign the value of newval to i, and then return True, otherwise, return False;

__sync_val_compare_and_swap(&i, oldval, newval)The principle is the same as the above function, but the return value becomes the value of i before the change;

__sync_lock_test_and_set(&i, newval)newval is written into i, and locks i, and returns the value of i before the operation;

__sync_lock_release(&i)Write 0 into i and unlock it;

operation result

image-20220516111851981

__sync_bool_compare_and_swap(&i, oldval, newval)It can be seen from the results that the value of i in the first call is the same as the value of oldval, then the value of newval 40 will be assigned to i, so i = 40 in the end; and the opposite is true in the second call, so the value of newval will not be assigned Give i, and notice that the return value of the function is the bool value corresponding to whether the exchange is successful or not

The i value of the first call __sync_val_compare_and_swap(&i, oldval, newval)is the same as the oldval value, then the value of newval 40 will be assigned to i, so in the end i = 40; while the second call is the opposite, so the value of newval will not be assigned to i, and notice The return value of the function is exactly the value of i before the change;

__sync_lock_test_and_set(&i, newval)write newval into i, and lock i, notice that new_i = 10, and return the i value 30 before the operation;

__sync_lock_release(&i)Write 0 into i and unlock it, notice that new_i = 0;

alg.18-3-syn-pthread-mutex.c

POSIX mutex

Mutex (mutual exclusion lock) belongs to the sleep-waiting type of lock . For example, there are two threads (thread A and thread B) on a dual-core machine, which run on Core0 and Core1 respectively. Assuming that thread A wants to obtain a lock in a critical section through the pthread_mutex_lock operation, and this lock is being held by thread B at this time, then thread A will be blocked, and Core0 will perform context switching (Context Switch) at this time. Thread A is placed in the waiting queue, and Core0 can run other tasks without busy waiting.

The context switch of the mutex can take a considerable amount of time, and if the lock is used for a very short time, the context switch is a huge overhead in comparison.

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
//#include <string.h>

#define MAX_N 40

static int count_1 = 0;
static int count_2 = 0;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    /* or declared: 
       pthread_mutex_t mutex;
       and in main():
       pthread_mutex_init(&mutex, NULL); */

void *thread_func1(void *arg)
{
    
    
    for (int i = 0; i < 20000; ++i) {
    
    
        pthread_mutex_lock(&mutex);
        count_1++;
        pthread_mutex_unlock(&mutex);
    }

    pthread_exit(NULL);
}

void *thread_func2(void *arg)
{
    
    
    for (int i = 0; i < 20000; ++i) {
    
    
        count_2++;
    }

    pthread_exit(NULL);
}

int main(void)
{
    
    
    pthread_t ptid_1[MAX_N];
    pthread_t ptid_2[MAX_N];

//    pthread_mutex_init(&mutex, NULL);

    for (int i = 0; i < MAX_N; ++i) {
    
    
        pthread_create(&ptid_1[i], NULL, &thread_func1, NULL);
    }

    for (int i = 0; i < MAX_N; ++i) {
    
    
        pthread_create(&ptid_2[i], NULL, &thread_func2, NULL);
    }

    for (int i = 0; i < MAX_N; ++i) {
    
    
        pthread_join(ptid_1[i], NULL);
    }

    for (int i = 0; i < MAX_N; ++i) {
    
    
        pthread_join(ptid_2[i], NULL);
    }

    pthread_mutex_destroy(&mutex);

    printf("result count_1 = %d\n", count_1);
    printf("result count_2 = %d\n", count_2);

    return 0;
}


Code description:

The mutex is pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZERstatically initialized, PTHREAD_MUTEX_INITIALIZER is a structure constant, or used pthread_mutex_init(&mutex, NULL)for dynamic initialization;

In the thread_func1 function, a mutex is used to solve the critical section conflict problem:

void *thread_func1(void *arg)
{
    
    
    for (int i = 0; i < 20000; ++i) {
    
    
        pthread_mutex_lock(&mutex);
        count_1++;
        pthread_mutex_unlock(&mutex);
    }

    pthread_exit(NULL);
}

In the 20,000 for loops, pthread_mutex_lock(&mutex)the lock is acquired through each loop, and then count_1 is incremented by 1, and then pthread_mutex_unlock(&mutex)the lock is released. During this process, there will be no conflict between threads;

In the thread_fun2 function, the mutex mechanism is not used, and the threads will conflict when calling the function;

Finally called pthread_mutex_destroy(&mutex)to release the mutex.

operation result

image-20220516113317304

From the results, the result of count_1 in the thread_func1 function is correct, but the count_2 in the thread_func2 function has a conflict, and the result is incorrect.

POSIX semaphores
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);

1. Named semaphores : can be shared between processes (similar to named pipes, usually used for inter-process communication)

alg.18-4-syn-pthread-sem-named.c

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <fcntl.h>

#define MAX_N 40

sem_t *named_sem; /* global long int pointer */

static int count_1 = 0;
static int count_2 = 0;

void *thread_func1(void *arg)
{
    
    
    for (int i = 0; i < 20000; ++i) {
    
    
        sem_wait(named_sem);
        count_1++;
        sem_post(named_sem);
    }

    pthread_exit(NULL);
}

void *thread_func2(void *arg)
{
    
    
    for (int i = 0; i < 20000; ++i) {
    
    
        count_2++;
    }

    pthread_exit(NULL);
}

int main(void)
{
    
    
    pthread_t ptid_1[MAX_N];
    pthread_t ptid_2[MAX_N];

    int i, ret;

    named_sem = sem_open("MYSEM", O_CREAT, 0666, 1);
        /* a file named "sem.MYSEM" is created in /dev/shm/ 
           to be shared by processes who can sense the file name */

    if(named_sem == SEM_FAILED) {
    
    
        perror("sem_open()");
        return EXIT_FAILURE;
    }

    for (int i = 0; i < MAX_N; ++i) {
    
    
        pthread_create(&ptid_1[i], NULL, &thread_func1, NULL);
    }

    for (int i = 0; i < MAX_N; ++i) {
    
    
        pthread_create(&ptid_2[i], NULL, &thread_func2, NULL);
    }

    for (i = 0; i < MAX_N; ++i) {
    
    
        pthread_join(ptid_1[i], NULL);
    }

    for (i = 0; i < MAX_N; ++i) {
    
    
        pthread_join(ptid_2[i], NULL);
    }

    printf("result count_1 = %d\n", count_1);
    printf("result count_2 = %d\n", count_2);

    sem_close(named_sem);
    sem_unlink("MYSEM"); /* remove sem.MYSEM from /dev/shm/ when its references is 0 */

    return 0;
}


Code description:

The function sem_open()is used to create a new or open an existing semaphore: named_sem = sem_open("MYSEM", O_CREAT, 0666, 1)a semaphore named "MYSEM" is created, the initial value is 1, and the return value is a semaphore descriptor;

In thread_func1()the function, there are also 20,000 loops, and each time it is called sem_wait(named_sem)to obtain the semaphore, the semaphore is decremented by 1 (P operation), and then enters the critical section to execute count_1++, and finally calls to sem_post(named_sem)release the semaphore, and the semaphore is added by 1 (V operation)

Executed last sem_close(named_sem)to close the semaphore.

operation result

image-20220516164117597

From the results, the count_1 result of the self-increment operation performed by using the semaphore in the thread_func1 function is correct, but the count_2 in the thread_func2 function has a conflict, and the result is incorrect.

2. Anonymous semaphore : (usually used for inter-thread communication)

int sem_init(sem_t *sem, int pshared, unsigned int value)
// 参数为: 1)信号量的指针 2)表示共享级别的标志 3)信号量的初始值
//pshared = 0表示此信号量只能由属于创建该信号量的同一进程的线程共享。
  

alg.18-5-syn-pthread-sem-unnamed.c

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>

#define MAX_N 40

sem_t unnamed_sem; /* global long int */

static int count_1 = 0;
static int count_2 = 0;

void *thread_func1(void *arg)
{
    
    
    for (int i = 0; i < 20000; ++i) {
    
    
        sem_wait(&unnamed_sem);
        count_1++;
        sem_post(&unnamed_sem);
    }

    pthread_exit(NULL);
}

void *thread_func2(void *arg)
{
    
    
    for (int i = 0; i < 20000; ++i) {
    
    
        count_2++;
    }

    pthread_exit(NULL);
}

int main(void)
{
    
    
    pthread_t ptid_1[MAX_N];
    pthread_t ptid_2[MAX_N];

    int i, ret;

    ret = sem_init(&unnamed_sem, 0, 1);
    if(ret == -1) {
    
    
        perror("sem_init()");
        return EXIT_FAILURE;
    }
    
    for (int i = 0; i < MAX_N; ++i) {
    
    
        pthread_create(&ptid_1[i], NULL, &thread_func1, NULL);
    }

    for (int i = 0; i < MAX_N; ++i) {
    
    
        pthread_create(&ptid_2[i], NULL, &thread_func2, NULL);
    }

    for (i = 0; i < MAX_N; ++i) {
    
    
        pthread_join(ptid_1[i], NULL);
    }

    for (i = 0; i < MAX_N; ++i) {
    
    
        pthread_join(ptid_2[i], NULL);
    }

    printf("result count_1 = %d\n", count_1);
    printf("result count_2 = %d\n", count_2);

    sem_destroy(&unnamed_sem);

    return 0;
}


Code description:

The function sem_init()is used to initialize the semaphore, and the address of the semaphore identifier sem_t unnamed_semis passed in: ret = sem_init(&unnamed_sem, 0, 1)a semaphore with an initial value of 1 is created, and this semaphore only belongs to the process.

In thread_func1()the function, there are also 20,000 loops, and each time it is called sem_wait(named_sem)to obtain the semaphore, the semaphore is decremented by 1 (P operation), and then enters the critical section to execute count_1++, and finally calls to sem_post(named_sem)release the semaphore, and the semaphore is added by 1 (V operation)

Executed last sem_destroy(named_sem)to destroy the semaphore.

operation result

image-20220516171152795

From the results, the count_1 result of the self-increment operation performed by using the semaphore in the thread_func1 function is correct, but the count_2 in the thread_func2 function has a conflict, and the result is incorrect.

producer and consumer problem

alg.18-6-syn-pc-con-6.c

/*  compiling with -pthread
    
    this version works properly
	file list:  syn-pc-con-7.h
		        syn-pc-con-7.c
		        syn-pc-producer-7.c
		        syn-pc-consumer-7.c
    with process shared memory and semaphores
    BUFFER_SIZE, MAX_ITEM_NUM, THREAD_PROD and THREAD_CONS got from input
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <string.h>
#include <sys/shm.h>
#include <semaphore.h>
#include <wait.h>
#include "alg.18-6-syn-pc-con-7.h"

int shmid;
void *shm = NULL;
int detachshm(void);
int random_code(unsigned int);

int main(void)
{
    
    
    int buffer_size, max_item_num, thread_prod, thread_cons;
    char code_str[10], pathname[80];
    int fd;
    key_t key;
    int ret;
    struct ctl_pc_st *ctl_ptr = NULL;
    struct data_pc_st *data_ptr = NULL;
    pid_t childpid, prod_pid, cons_pid;
		
    while (1) {
    
     /* env. initialization */
        printf("Pls input the number of items as the buffer bound(1-100, 0 quit): ");
        scanf("%d", &buffer_size);
        if(buffer_size <= 0)
            return 0;
        if(buffer_size > 100)
            continue;
        printf("Pls input the max number of items to be produced(1-10000, 0 quit): ");
        scanf("%d", &max_item_num);
        if(max_item_num <= 0)
            return 0;
        if(max_item_num > 10000)
            continue;
        printf("Pls input the number of producers(1-500, 0 quit): ");
        scanf("%d", &thread_prod);
        if(thread_prod <= 0)
            return 0;
        printf("Pls input the number of consumers(1-500, 0 quit): ");
        scanf("%d", &thread_cons);
        if(thread_cons <= 0)
            return 0;

        break;
    }
    
    sprintf(code_str, "%d", random_code(4));
    strcpy(pathname, "/tmp/shm-");
    strcat(pathname, code_str);
    printf("shm pathname: %s\n", pathname);
    fd = open(pathname, O_CREAT);
	if(fd == -1) {
    
    
        perror("pathname fopen()");
        return EXIT_FAILURE;
    }

    if((key = ftok(pathname, 0x28)) < 0) {
    
     
        perror("ftok()");
        exit(EXIT_FAILURE);
    }
    
      /* get the shared memoey
         'invalid argument' if the size exceeds the current shmmax.
	     when the shmid exists, its size is defined when it was firstly declared and can not be changed.
	     if you want a lager size, you have to alter a new key for a new shmid.
      */
    shmid = shmget((key_t)key, (buffer_size + BASE_ADDR)*sizeof(struct data_pc_st), 0666 | IPC_CREAT);
    if(shmid == -1) {
    
    
        perror("shmget()");
        exit(EXIT_FAILURE);
    }

      /* attach the created shared memory to user space */
    shm = shmat(shmid, 0, 0);
    if(shm == (void *)-1) {
    
    
        perror("shmat()");
        exit(EXIT_FAILURE);
    }

      /* set the shared memory, initialize all control parameters */
    ctl_ptr = (struct ctl_pc_st *)shm;
    data_ptr = (struct data_pc_st *)shm;

    ctl_ptr->BUFFER_SIZE = buffer_size;
    ctl_ptr->MAX_ITEM_NUM = max_item_num;
    ctl_ptr->THREAD_PROD = thread_prod;
    ctl_ptr->THREAD_CONS = thread_cons; 
    ctl_ptr->prod_num = 0;
    ctl_ptr->cons_num = 0;
    ctl_ptr->enqueue = 0;
    ctl_ptr->dequeue = 0;
    ctl_ptr->END_FLAG = 0;

    ret = sem_init(&ctl_ptr->sem_mutex, 1, 1); /* pshared set to non-zero for inter process sharing */
    if(ret == -1) {
    
    
        perror("sem_init-mutex");
        return detachshm();
    }
    ret = sem_init(&ctl_ptr->sem_stock, 1, 0); /* initialize to 0 */
    if(ret == -1) {
    
    
        perror("sem_init-stock");
        return detachshm();
    }
    ret = sem_init(&ctl_ptr->sem_emptyslot, 1, ctl_ptr->BUFFER_SIZE); /*initialize to BUFFER_SIZE */
    if(ret == -1) {
    
    
        perror("sem_init-emptyslot");
        return detachshm();
    }

    printf("\nsyn-pc-con console pid = %d\n", getpid());

    char *argv1[3];
    char execname[] = "./";
    char shmidstring[10];
    sprintf(shmidstring, "%d", shmid);
    argv1[0] = execname;
    argv1[1] = shmidstring;
    argv1[2] = NULL;
        
    childpid = vfork();
    if(childpid < 0) {
    
    
        perror("first fork");
        detachshm();
        unlink(pathname);
        return 0;
    } 
    else if(childpid == 0) {
    
     /* call the producer */ 
        prod_pid = getpid();
        printf("producer pid = %d, shmid = %s\n", prod_pid, argv1[1]);
        execv("./alg.18-7-syn-pc-producer-7.out", argv1);
    }
    else {
    
    
        childpid = vfork();
        if(childpid < 0) {
    
    
            perror("second vfork");
            detachshm();
            unlink(pathname);
            return 0;
        } 
        else if(childpid == 0) {
    
     /* call the consumer */
            cons_pid = getpid();
            printf("consumer pid = %d, shmid = %s\n", cons_pid, argv1[1]);
            execv("./alg.18-8-syn-pc-consumer-7.out", argv1);
        }
    }

    if(waitpid(prod_pid, 0, 0) != prod_pid) {
    
    /* block wait */
        perror("wait prod");
    } else {
    
    
        printf("waiting prod_pid %d success.\n", prod_pid);
    }

    if (waitpid(cons_pid, 0, 0) != cons_pid) {
    
    
        perror("wait cons");
    } else {
    
    
        printf("waiting cons_pid %d success.\n", cons_pid);
    }

    ret = sem_destroy(&ctl_ptr->sem_mutex);
    if(ret == -1) {
    
    
        perror("sem_destroy sem_mutex");
    }
    ret = sem_destroy(&ctl_ptr->sem_stock); /* sem_destroy() will not affect the sem_wait() calling process */
    if(ret == -1) {
    
    
        perror("sem_destroy stock");
    }
    ret = sem_destroy(&ctl_ptr->sem_emptyslot);
    if(ret == -1) {
    
    
        perror("sem_destroy empty_slot");
    }

    detachshm();
    unlink(pathname);
    return EXIT_SUCCESS;
}

int detachshm(void)
{
    
    
    if(shmdt(shm) == -1) {
    
    
        perror("shmdt()");
        exit(EXIT_FAILURE);
    }

    if(shmctl(shmid, IPC_RMID, 0) == -1) {
    
    
        perror("shmctl(IPC_RMID)");
        exit(EXIT_FAILURE);
    }

    return EXIT_SUCCESS;
}

int random_code(unsigned int code_len)
{
    
    
	int code_val;
	long int modulus = 1;
	
	for (int i = 0; i < code_len; i++) {
    
    
		modulus = modulus * 10;
	}
	
	srand(time(NULL));
    while (1) {
    
    
        code_val = rand() % modulus;
        if(code_val > modulus / 10 - 1) {
    
    
            break;
        }
    }
    
	return code_val;
}


Code description:

Queue operations: (enqueue | dequeue) % buffer_size + BASE_ADDR

First, the user is required to input the buffer size, the number of produced items, the number of producers, and the number of consumers;

Then call shmid = shmget((key_t)key, (buffer_size + BASE_ADDR)*sizeof(struct data_pc_st), 0666 | IPC_CREAT)the key obtained according to the previously randomly generated file name to create a shared memory space with a size of (buffer_size + BASE_ADDR)*sizeof(struct data_pc_st);

Call again shm = shmat(shmid, 0, 0)to map the shared memory area to the address space of the calling process and let the process access it;

Then set the shared memory, which is the control structure and the data structure respectively. These two structures will be shared with the child processes that come out of vfork later:

ctln = (struct ctln_pc_st *)shm;
data = (struct data_pc_st *)shm;

Then initialize the mutex semaphore in the control structure, the buffer storage quantity semaphore, and the number of free units in the buffer semaphore:

ret = sem_init(&ctln->sem_mutex, 1, 1)
ret = sem_init(&ctln->stock, 1, 0);
ret = sem_init(&ctln->emptyslot, 1, ctln->BUFFER_SIZE);

Note that pshare is specified as 1 (not 0) in the initialization of the mutex semaphore (ctln->sem_mutex), indicating that this is used for inter-process sharing and initialized to 1;

The semaphore (ctln->stock) of the buffer storage quantity is also pshare to 1 and initialized to 0;

The semaphore (ctln->emptyslot) indicating the number of free units in the buffer is initialized to BUFFER_SIZE

After the initialization work is completed, the process is created through vfork(), and then execv()used to call the producer and consumer programs, that execv("./alg.18-8-syn-pc-consumer-7.out", argv1)is, execv("./alg.18-7-syn-pc-producer-7.out", argv1)the argv1 parameter here is shmid;

Producer:

alg.18-7-syn-pc-producer-7.c

/*  compiling with -pthread

    this version works properly
    file list:  syn-pc-con-7.h
                syn-pc-con-7.c
                syn-pc-producer-7.c
                syn-pc-consumer-7.c
    with process shared memory and semaphores
*/

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/shm.h>
#include <semaphore.h>
#include <unistd.h>
#include <sys/syscall.h>
#include "alg.18-6-syn-pc-con-7.h"
#define gettid() syscall(__NR_gettid)

struct ctl_pc_st *ctl_ptr = NULL;
struct data_pc_st *data_ptr = NULL;

static int item_sn;
int random_code(unsigned int);

void *producer(void *arg)
{
    
    
  
  //当已经生产的产品数量小于所要生产的产品数量时,进入生产循环
    while (ctl_ptr->prod_num < ctl_ptr->MAX_ITEM_NUM) {
    
    
        //这里的sem_emptyslot在此前被初始化为BUFFER_SIZE
        sem_wait(&ctl_ptr->sem_emptyslot); //若缓冲区的空闲单元信号量大于0,则可以存放产品,这里将该信号量减1,否则需要等待
        sem_wait(&ctl_ptr->sem_mutex); //若互斥信号量小于1,则需要等待,这里将该信号量减1
        if(ctl_ptr->prod_num < ctl_ptr->MAX_ITEM_NUM) {
    
    
            ctl_ptr->prod_num++;	//产品数加1
            (data_ptr + ctl_ptr->enqueue + BASE_ADDR)->prod_tid = gettid(); // 将产品的线程号和序列号入队
            (data_ptr + ctl_ptr->enqueue + BASE_ADDR)->item_sn = item_sn++;
            printf("producer tid %ld prepared item_sn %d, now enqueue = %d\n", (data_ptr + ctl_ptr->enqueue + BASE_ADDR)->prod_tid, (data_ptr + ctl_ptr->enqueue + BASE_ADDR)->item_sn, ctl_ptr->enqueue);
            ctl_ptr->enqueue = (ctl_ptr->enqueue + 1) % ctl_ptr->BUFFER_SIZE;
          //当已经生产的产品数量等于所要生产的产品数量时,将结束标志END_FLAG置为1
            if(ctl_ptr->prod_num == ctl_ptr->MAX_ITEM_NUM) {
    
     
                ctl_ptr->END_FLAG = 1;
		    }
            sem_post(&ctl_ptr->sem_stock);  //将缓冲区存储数量的信号量加1
        } else {
    
     //当已经生产的产品数量不小于所要生产的产品数量时,将表示缓冲区的空闲单元信号量加1
            sem_post(&ctl_ptr->sem_emptyslot); 
        }
        sem_post(&ctl_ptr->sem_mutex); // 释放互斥信号量,允许其它线程访问临界区
        usleep(random_code(6));
    }

    pthread_exit(NULL);
}

int main(int argc, char *argv[])
{
    
    
    int shmid;
    void *shm = NULL;
    int i, ret;

    shmid = strtol(argv[1], NULL, 10); /* shmid delivered */
    shm = shmat(shmid, 0, 0);
    if(shm == (void *)-1) {
    
    
        perror("\nproducer shmat()");
        exit(EXIT_FAILURE);
    }

    ctl_ptr = (struct ctl_pc_st *)shm;
    data_ptr = (struct data_pc_st *)shm;

    pthread_t ptid[ctl_ptr->THREAD_PROD];

    item_sn = random_code(8);

    for (i = 0; i < ctl_ptr->THREAD_PROD; ++i) {
    
    
        ret = pthread_create(&ptid[i], NULL, &producer, NULL);
        if(ret != 0) {
    
    
            perror("producer pthread_create()");
            break;
        }
    }    

    for (i = 0; i < ctl_ptr->THREAD_PROD; ++i) {
    
    
        pthread_join(ptid[i], NULL);
    }

    for (i = 0; i < ctl_ptr->THREAD_CONS - 1; ++i) {
    
    
      /* all producers stop working, in case some consumer takes the last stock
         and no more than THREAD_CON-1 consumers stick in the sem_wait(&stock) */
        sem_post(&ctl_ptr->sem_stock);
    }
    
    if(shmdt(shm) == -1) {
    
    
        perror("producer shmdt()");
        exit(EXIT_FAILURE);
    }

    exit(EXIT_SUCCESS);
}

int random_code(unsigned int code_len)
{
    
    
	int code_val;
	long int modulus = 1;
	
	for (int i = 0; i < code_len; i++) {
    
    
		modulus = modulus * 10;
	}
	
	srand(time(NULL));
    while (1) {
    
    
        code_val = rand() % modulus;
        if(code_val > modulus / 10 - 1) {
    
    
            break;
        }
    }
    
	return code_val;
}

Code description:

This part of the code corresponds to the asynchronous production program of THREAD_PROD producer threads. When the condition ctl_ptr->prod_num < ctl_ptr->MAX_ITEM_NUMis met, that is, when the number of products already produced is less than the number of products to be produced, the production code is called circularly, and the corresponding product is inserted into the circular queue. When ctl_ptr->prod_num == ctl_ptr->MAX_ITEM_NUM, that is, when the quantity of products produced is equal to the quantity of products to be produced, the production ends.

The specific details are described in the comments of the code;

consumer:

alg.18-8-syn-pc-consumer-7

/*  compiling with -pthread

    this version works properly
    file list:  syn-pc-con-7.h
                syn-pc-con-7.c
                syn-pc-producer-7.c
                syn-pc-consumer-7.c
    with process shared memory and semaphores 
*/

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/shm.h>
#include <semaphore.h>
#include <unistd.h>
#include <sys/syscall.h>
#include "alg.18-6-syn-pc-con-7.h"
#define gettid() syscall(__NR_gettid)

struct ctl_pc_st *ctl_ptr = NULL;
struct data_pc_st *data_ptr = NULL;

int random_code(unsigned int);

void *consumer(void *arg)
{
    
    
   //当消费者已经消费的产品数小于生产者生产的产品数或者生产者还没生产结束时
    while ((ctl_ptr->cons_num < ctl_ptr->prod_num) || (ctl_ptr->END_FLAG == 0))  {
    
     
      //当缓冲区产品存储数量的信号量大于0时,表示当中有产品可以消费,将该信号量减1
        sem_wait(&ctl_ptr->sem_stock);  /* if stock is empty and all producers stop working at this point, one or more consumers may wait forever */
      //若互斥信号量不小于1,则进入临界区,否则需要等待。
        sem_wait(&ctl_ptr->sem_mutex);
      //当消费者已经消费的产品数小于生产者生产的产品数
        if (ctl_ptr->cons_num < ctl_ptr->prod_num) {
    
     
            printf("\t\t\t\tconsumer tid %ld taken item_sn %d by tid %ld, now dequeue = %d\n", gettid(), (data_ptr + ctl_ptr->dequeue + BASE_ADDR)->item_sn, (data_ptr + ctl_ptr->dequeue + BASE_ADDR)->prod_tid, ctl_ptr->dequeue);
           //将产品出队
            ctl_ptr->dequeue = (ctl_ptr->dequeue + 1) % ctl_ptr->BUFFER_SIZE;
          //消费的产品数加1
            ctl_ptr->cons_num++;
          //将表示缓冲区空闲单元的信号量加1
            sem_post(&ctl_ptr->sem_emptyslot);
          
          // 当消费者已经消费的项目数量不小于生产者已经生产的项目数量,将表示缓冲区中存储数量的信号量加1
        } else {
    
    
            sem_post(&ctl_ptr->sem_stock);
        }
      //释放互斥信号量
        sem_post(&ctl_ptr->sem_mutex);
        usleep(random_code(6));
    }
    
    pthread_exit(0);
}

int main(int argc, char *argv[])
{
    
    
    int shmid;
    void *shm = NULL;
    int i, ret;

    shmid = strtol(argv[1], NULL, 10); /* shmnid delivered */
    shm = shmat(shmid, 0, 0);
    if (shm == (void *)-1) {
    
    
        perror("consumer shmat()");
        exit(EXIT_FAILURE);
    }

    ctl_ptr = (struct ctl_pc_st *)shm;
    data_ptr = (struct data_pc_st *)shm;

    pthread_t ptid[ctl_ptr->THREAD_CONS];

    for (i = 0; i < ctl_ptr->THREAD_CONS; ++i) {
    
    
        ret = pthread_create(&ptid[i], NULL, &consumer, NULL); 
        if (ret != 0) {
    
    
            perror("consumer pthread_create()");
            break;
        }
    } 
	
    for (i = 0; i < ctl_ptr->THREAD_CONS; ++i) {
    
    
        pthread_join(ptid[i], NULL);
    }

    if (shmdt(shm) == -1) {
    
    
        perror("consumer shmdt()");
        exit(EXIT_FAILURE);
    }
    
    exit(EXIT_SUCCESS);
}

int random_code(unsigned int code_len)
{
    
    
	int code_val;
	long int modulus = 1;
	
	for (int i = 0; i < code_len; i++) {
    
    
		modulus = modulus * 10;
	}
	
	srand(time(NULL));
    while (1) {
    
    
        code_val = rand() % modulus;
        if(code_val > modulus / 10 - 1) {
    
    
            break;
        }
    }
    
	return code_val;
}



Code description:

This part of the code corresponds to the asynchronous consumption program of THREAD_CONS consumer threads. When the condition ctl_ptr->cons_num < ctl_ptr->prod_num) || (ctl_ptr->END_FLAG == 0is met, that is, when the number of products consumed is less than the number of products produced or the production is not over, the consumption code is called in a loop to transfer the corresponding products from the loop pop in the queue.

The specific details are described in the comments of the code;

operation result

image-20220516211405924

As can be seen from the running results, based on the buffer size of 4, the number of productions of 8, the number of producers of 2, and the number of consumers of 3, the producer and consumer are asynchronous until the producer produces 8 When the product is consumed by the consumer, the program ends.

Guess you like

Origin blog.csdn.net/m0_52387305/article/details/124841564