Multithreaded programming and producer consumer issues

Posted by scottfossum on Mon, 07 Mar 2022 14:49:27 +0100

1, Thread theory foundation

Advantages of threads

One of the reasons to use multithreading:

Compared with the process, it is a very frugal way of multitasking. Under the Linux system, to start a new process, it must be allocated an independent address space, and a large number of data tables must be established to maintain its code segments, stack segments and data segments
A process has multiple threads, and the switching time between threads is very small. The overhead of a process is 30 times that of a thread

The second reason to use multithreading:

Convenient communication mechanism between threads. Threads in the same process share data space, so the data of one thread can be directly used by other threads (except local variables and formal parameters). The process has an independent data space, and the data transmission can only be carried out through inter process communication
Other reasons:
Make the multi cpu system (multi-core) more effective. The operating system will ensure that different threads run on different CPUs when the number of threads is not greater than the number of CPUs
Improve the program structure. A long and complex process can be divided into multiple threads and become several independent or semi independent running parts. Such a program will be conducive to understanding and modification

Required header file

Multithreading under Linux system follows POSIX thread interface, which is called pthread. The header file pthread is required to write multithreading program under Linux h. Libpthread is required for connection h
You need to add - lpthread when compiling gcc

2, Multithreaded programming

establish

#include<pthread.h>
int pthread_create(pthread_t *tidp,const pthread_attr_t* attr,void*(*start_rtn)(void),void *arg)

Parameters:
tidp: thread id, the incoming address is an address (output)
attr: thread attribute (usually NULL)
start_rtn: the function to be executed by the thread, which is of type (void *)
arg:start_rtn parameter (void *) type, NULL if none
The return value is 0 successfully

sign out

If exit is invoked in any thread in the process, or Exit, then the whole process will stop. The normal exit modes of threads are:
(1) Thread returned from Startup
(2) A thread can be terminated by another process
(3) The thread itself can call pthread_exit function terminated
pthread_exit function

#include<pthread.h>
void pthread_exit(void *rval_ptr)

Function: terminate calling thread
Rval_ptr: pointer to the return value of thread exit

Thread waiting

int pthread_join(pthread_t tid,void **rval_ptr);

Function: block the calling thread until the specified thread terminates
tid: thread id waiting to be pushed
rval_ptr: pointer (secondary pointer) of the return value of the thread exit. If there is no pointer, it is NULL
The return value is 0, failed; Otherwise, return value

3, Thread synchronization

Multithreaded programming, because it is impossible to know which thread will operate on the shared resources at which time, so how to protect the shared resources becomes complex
Resource competition between threads:
1 mutex
2 signal lamp
3 conditional variables

mutex

establish

Mutex cannot be defined in the main function, but should be defined globally
The mutex usage type is pthread_mutex_t indicates. It needs to be initialized before use;
For statically allocated mutex, you can set it as the default mutex object PTHREAD_MUTEX_INITIALLIZER
For dynamically allocated mutexes, after applying for memory (malloc), pthread is used_ mutex_ Init initializes, and pthread needs to be called before free ing memory_ mutex_ destroy

initialization

#include<pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);

Lock

For access to shared resources, the mutex should be locked. If the mutex is locked, the calling thread will block until the mutex is unlocked.

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);

Return value: 0 for success and error number for error.
Trylock is a non blocking call mode. If the mutex is not locked, the trylock function will lock the mutex and obtain access to shared resources; If the mutex is locked, the trylock function will not block the wait and directly return EBUSY, indicating that the shared resource is busy

Unlock

After the operation is completed, the mutex must be unlocked, that is, the release mentioned above. In this way, other threads waiting for the lock will have the chance to obtain the lock, otherwise other threads will block forever.

int pthread_mutex_unlock(pthread_mutex_t *mutex)

Destroy

pthread_mutex_destroy(pthread_mutex_t *mutex);

Conditional variable

establish

Define variable pthread_cond_t cond;

Conditional variable initialization

 a,initiate static
  PTHREAD_COND_INITIALIZER
 b,dynamic initialization
  int pthread_cond_init(pthread_cond_t * cond,pthread_condattr_tond_attr);

Conditional variable undo

int pthread_cond_destroy(pthread_cond_t *cond);

Conditional variable wait

int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);

Send signal

int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);

Process: Unlock - > wait - > lock

Thread 1

 pthread_mutex_lock(&mutex);
 if//(the condition is not tenable)
 pthread_cond_wait(&cond,*mutex);
  //modify condition
 pthread_mutex_unlock(&mutex);     

Thread 2

pthread_mutex_lock(&mutex);
//Make the conditions meet and send the signal
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);

4, Producer consumer issues

//Simulate producers producing bread and consumers eating bread
//Create two processes, one for production and one for consumption
//Use a circular queue to store bread
//When the producer produces bread, the thread is locked and the consumer cannot eat bread. After production, the thread is unlocked and the consumer can eat bread. When the production of bread is enough (the team is full), the producer does not produce and waits
//When enough bread is produced (the queue is full), the consumer eats bread. At this time, the thread is locked and the producer cannot produce until the bread is eaten (the queue is empty)
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<errno.h>
#define BUFFSIZE 16
#define OVER -1
struct procon
{
    int buffer[BUFFSIZE];//Store bread, up to 16
    int front,rear;//Circular queue pointer, used to judge empty and full
    pthread_mutex_t mutex;//mutex 
    pthread_cond_t noempty;//Condition variable, not null
    pthread_cond_t nofull;//Conditional variable, dissatisfaction
};
typedef struct procon P;
P buf;//Variables that define structure types

void init_all(P *buf)//initialization
{
    pthread_mutex_init(&buf->mutex,NULL);
    pthread_cond_init(&buf->noempty,NULL);
    pthread_cond_init(&buf->nofull,NULL);
    buf->front = buf->rear = 0;
}
void put_in(P *buf,int data)
{
    //Lock
    pthread_mutex_lock(&buf->mutex);
    //When the sentence is full
    if ((buf->rear + 1) % BUFFSIZE == buf->front)
    {//Release the lock and wait for another thread to give a signal that it is not full. After being awakened, it will take back the lock
        pthread_cond_wait(&buf->nofull,&buf->mutex);
    }
    //When it's not full, put the bread on the shelf
    buf->buffer[buf->rear] = data;
    buf->rear = (buf->rear + 1) % BUFFSIZE;
    pthread_cond_signal(&buf->noempty);
    //Unlock
    pthread_mutex_unlock(&buf->mutex);
}
int get_from(P *buf)
{
    int data;
    pthread_mutex_lock(&buf->mutex);
    //When empty
    if (buf->front == buf->rear)
    {
        pthread_cond_wait(&buf->noempty,&buf->mutex);
    }
    //When not empty
    data = buf->buffer[buf->front];
    buf->front = (buf->front + 1) % BUFFSIZE;
    pthread_cond_signal(&buf->nofull);
    pthread_mutex_unlock(&buf->mutex);
    return data;
}
void *produce_tid(void)
{
    int i;
    //The production team produces only 20 pieces of bread
    for ( i = 0; i < 20; i++)
    {
        sleep(rand()%3);//Random production time
        printf("%d.one bread is producted\n",i + 1);
        put_in(&buf,i + 1);
    }
    put_in(&buf,OVER);
    return NULL;
}
void *contumer_tid(void)
{
    int d;
    while (1)
    {
        sleep(rand()%3);//Eat bread at random
        d = get_from(&buf);
        if (d == OVER)
        {
            break;
        }
        printf("%d.eat a bread\n",d);
    }
    return NULL;
}
int main()
{
    pthread_t tid_p,tid_c;//Create two thread producers and consumers
    init_all(&buf);//Call the function to initialize the mutex and condition variables
    //Create thread  
    pthread_create(&tid_p,NULL,(void *)produce_tid,NULL);
    pthread_create(&tid_c,NULL,(void *)contumer_tid,NULL);
    //Wait for the thread to end
    pthread_join(tid_c,NULL);
    pthread_join(tid_p,NULL);
    return 0;
}

Topics: Linux Windows Operation & Maintenance