linux: threading & & multithreading

Posted by JTapp on Sat, 19 Feb 2022 02:06:42 +0100

thread

1. Linux thread concept

A thread is a subtask that runs independently in a process. It can be understood as a "lightweight process". There can be multiple threads in a process, and these threads use the same PCB.

1. An execution route in a program is called a thread. A more accurate definition is that a thread is "a sequence of control within a process".

2. All processes have at least one execution thread.

3. The thread runs inside the process, which essentially runs in the process address space.

4. In the Linux system, in the eyes of the CPU, the PCB should be lighter than the traditional process.

5. Through the process virtual address space, you can see most of the resources of the process, and reasonably allocate the process resources to each execution flow to form a thread execution flow.

2. Create thread

Function: create a new thread
Prototype:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);

parameter
Thread: returns the thread ID.
Attr: set the property of the thread. If attr is NULL, the default property will be used.
start_routine: is a function address, which is the function to be executed after the thread is started.
arg: the parameter passed to the thread startup function.
Return value: 0 is returned successfully; An error code is returned for failure.

3. Simply understand the thread with a short piece of code

The following code creates a main thread and a thread_run thread. After running, it can be found that the two threads use the same PCB (the same pid)

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h> 
//The tid of a thread represents the starting address of the thread
void *thread_run(void *arg)
{
  while(1)
  {
    printf("I am %s;pid:%d,my thread id:%p\n",(char*)arg,getpid(),pthread_self());
    sleep(1);
  }
}
//thread_ The run execution flow and the main execution flow use the same pid
int main()
{
  pthread_t tid;
  //Create a thread and put the thread id in tid
  pthread_create(&tid,NULL,thread_run,(void*)"thread 1");
  while(1)
  {
     printf("I am main thread id: %p,new thread id: %p, pid:%d\n",pthread_self(),tid,getpid());
     sleep(1);
  }
  return 0;
}

4. Advantages & & disadvantages of threads

advantage

1. The cost of creating a new thread is much less than that of creating a new process.
2. Compared with switching between processes, switching between threads requires much less work from the operating system.
3. Threads occupy much less resources than processes.
4. Be able to make full use of the parallelizable number of multiprocessors.
5. While waiting for the end of slow I/O operation, the program can perform other computing tasks.
6. For computing intensive applications, in order to run on multiprocessor systems, computing is decomposed into multiple threads.
7. For I/O-Intensive applications, I/O operations are overlapped in order to improve performance. Threads can wait for different I/O operations at the same time.

shortcoming

1. Performance loss; A compute intensive thread that is rarely blocked by external events often cannot share the same processor with other threads. If the number of computing intensive threads is more than the available processors, there may be a large performance loss. Here, the performance loss refers to the additional synchronization and scheduling overhead, while the available resources remain unchanged.
2. Reduced robustness; Writing multithreading requires more comprehensive and in-depth consideration. In a multithreaded program, there is a great possibility of adverse effects due to subtle deviations in time allocation or sharing variables that should not be shared. In other words, there is a lack of protection between threads.
3. Lack of access control; Process is the basic granularity of access control, and invoking some system call functions in a thread will have an impact on the whole process.
4. Increased programming difficulty; Writing and debugging a multithreaded program is much more difficult than a single threaded program.

Reasonable use of multithreading can improve the execution efficiency of CPU intensive programs and the user experience of IO intensive programs

deadlock

If you want multiple threads to safely access the same critical resource, you need to lock the thread

1. Deadlock concept

It refers to a state in which each process is waiting for a set of resources to be released but will not be permanently occupied by other processes.

It can be understood that two threads a and b want to access and modify the same critical resource x, but this resource only allows one thread to access and modify at a time. Therefore, two threads a and b are provided with an access allowed condition ax and bx respectively. When the ax condition is met, thread a holds a lock to access and modify the critical resource. At this time, thread b cannot enter this critical resource, After a modifies X so that the ax condition is not satisfied, thread a releases the lock, and then thread b holds the lock and enters the critical resource X.
It can be extended to more threads.

2. Four necessary conditions for deadlock

1. Mutually exclusive condition: a resource can only be used by one execution flow at a time.
2. Request and hold condition: when an execution flow is blocked by requesting resources, it will hold the obtained resources.
3. Conditions of non deprivation: the resources obtained by an execution flow cannot be forcibly deprived until they are used up at the end.
4. Circular waiting condition: a circular waiting resource relationship is formed between several execution streams.

3. Conditional variable function of lock

1. Initialization
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict
attr);
Parameters:
cond: condition variable to initialize
attr: NULL
2. Destruction
int pthread_cond_destroy(pthread_cond_t *cond)
3. Waiting for conditions to be met
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
Parameters:
cond: wait on this condition variable
Mutex: mutex
4. Wake up waiting
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

4. Mutual exclusion

Why pthread_cond_wait needs mutex?
Conditional waiting is a means of synchronization between threads. If there is only one thread, if the condition is not satisfied, it will not be satisfied all the time. Therefore, a thread must change the shared variable through some operations to meet the previously unsatisfied condition, and friendly notify the thread waiting on the condition variable.
Conditions will not suddenly become satisfied for no reason, which will inevitably involve changes in shared data. So be sure to protect it with a mutex lock. Shared data cannot be obtained and modified safely without mutual exclusion.

Producer consumer model

321 principle:
Three relationships: producer VS producer (mutually exclusive); Producer VS consumer (mutually exclusive & & synchronization); Consumer VS consumer (mutually exclusive)
Two roles: producer; consumer
One scenario: critical resources

1. Why use the producer consumer model

Producer consumer model is to solve the strong coupling problem between producers and consumers through a container. Producers and consumers do not communicate directly with each other, but communicate through the blocking queue. Therefore, after producing data, producers do not need to wait for consumers to process, but directly throw it to the blocking queue. Consumers do not ask producers for data, but directly take it from the blocking queue. The blocking queue is equivalent to a buffer, which balances the processing capacity of producers and consumers. This blocking queue is used to decouple producers and consumers.

2. Advantages of producer consumer model

Decoupling; Support concurrency; Uneven busy and free time is supported.

3. Production and consumption model of queue simulating blocking queue

#ifndef __QUEUE_BLOCK_H__
#define __QUEUE_BLOCK_H__
#include<unistd.h>
#include<iostream>
#include<pthread.h>
#include<queue>
using namespace std;
class Task
{
public:
  int x;
  int y;
public:
  Task(int _x,int _y):x(_x),y(_y)
  {}
  Task()
  {}
  int Run()
  {
    return x+y;
  }
  ~Task()
  {}
};
class BlockQueue
{
private:
  queue<Task> q;
  size_t cap;
  pthread_mutex_t lock;
  pthread_cond_t c_cond;
  pthread_cond_t p_cond;
public:
  //Full sentence
  bool IsFull()
  {
    return q.size()>=cap;
  }
  //Air judgment
  bool IsEmpty()
  {
    return q.empty();
  }
  //Unlock
  void UnlockQueue()
  {
    pthread_mutex_unlock(&lock);
  }
  //Awaken consumers
  void WakeUpConsumer()
  {
    cout<<"WakeUpConsumer"<<endl;
    pthread_cond_signal(&c_cond);
  }
  //Wake up producers
  void WakeUpProductor()
  {
    cout<<"WakeUpProductor"<<endl;
    pthread_cond_signal(&p_cond);
  }
  //Consumer waiting
  void ConsumerWait()
  {
    cout<<"ConsumerWait"<<endl;
    pthread_cond_wait(&c_cond,&lock);
  }
  //Producer waiting
  void ProductWait()
  {
    cout<<"ProductWait"<<endl;
    pthread_cond_wait(&p_cond,&lock);
  }
public:
    BlockQueue(int _cap):cap(_cap)
    {
      pthread_mutex_init(&lock,nullptr);
      pthread_cond_init(&c_cond,nullptr);
      pthread_cond_init(&p_cond,nullptr);
    }
    ~BlockQueue()
    {
      pthread_mutex_destroy(&lock);
      pthread_cond_destroy(&p_cond);
      pthread_cond_destroy(&c_cond);
    }
    //Consumers consume, take or execute the data and tasks in the queue and execute them
    void Put(Task in)
    {
      UnlockQueue();
      if(IsFull())
      {
        WakeUpConsumer();
        ProductWait();
      }
      q.push(in);
      UnlockQueue();
    }
    //The producer produces and fills the queue with data or tasks
    void Get(Task &out)
    {
      UnlockQueue();
      if(IsEmpty())
      {
        WakeUpProductor();
        ConsumerWait();
      }
      out=q.front();
      q.pop();
      UnlockQueue();
    }
};
#endif
#include "block.h"
using namespace std;

void *consumer_run(void *arg)
{
  //int num=(int)arg;
  pthread_mutex_t lock;
  BlockQueue *bq=(BlockQueue*)arg;
  while(true)                         
  {                            
    pthread_mutex_lock(&lock);
    //int data;
    Task t;
    bq->Get(t); 
    //t.Run();
    pthread_mutex_unlock(&lock);
    cout<<"consumer"<<t.x<<"+"<<t.y<<"="<<t.Run()<<endl;
    sleep(1); 
  }
  //pthread_mutex_unlock(&lock);
}
void *productor_run(void *arg)
{
  //int num=(int)arg;
  pthread_mutex_t lock;
  sleep(1);
  BlockQueue *bq=(BlockQueue*)arg;
  //int count=0;
  while(true)
  {
    int x=rand()%10+1;
    int y=rand()%100+1;
    pthread_mutex_lock(&lock);
    Task t(x,y);
    bq->Put(t);
    pthread_mutex_unlock(&lock);
    cout<<"productor "<<x<<"+"<<y<<"=?"<<endl;
  }
  //pthread_mutex_unlock(&lock);
}

int main()
{
  BlockQueue *bq = new BlockQueue(5);
  pthread_t con1,pro1;
  pthread_create(&con1,nullptr,consumer_run,(void*)bq);
  pthread_create(&pro1,nullptr,productor_run,(void*)bq);
  //pthread_create(&con2,nullptr,consumer_run,(void*)bq);
  //pthread_create(&pro2,nullptr,productor_run,(void*)bq);
  pthread_join(con1,nullptr);
  //pthread_join(con2,nullptr);
  pthread_join(pro1,nullptr);
  //pthread_join(pro2,nullptr);
  return 0;
}

Topics: Linux Multithreading queue