[Operating system principle]–Thread synchronization

Directory of series articles

[Operating system principle]–Process management
[Operating System Principle]–Process Scheduling
[Operating system principle]–Thread synchronization
[Operating system principle]–Semaphore and PV operation implementation
[Operating system principle]–Linux memory management
[Operating system principle]–File system programming

Thread synchronization

  • Table of Contents of Series Articles
  • Experiment related knowledge
    • 1. Processes and threads
    • 2. Thread synchronization
    • 3.Multi-threading
    • 4. Thread related functions
    • 5.Thread attributes
  • Experimental equipment and software environment
  • Experiment content
    • Question requirements
    • my thoughts
  • Abnormal problems and solutions

Experiment related knowledge

1. Process and thread

Process:

  • Definition: A process is an independent execution unit in the operating system. Each process has independent memory space, program code, data and system resources.
  • Resource independence: Processes are independent of each other, and the crash of one process will not directly affect other processes.
    Switching cost: The cost of process switching is relatively high because complete context information, including memory, registers, etc., needs to be saved and restored during switching.
  • Communication: Inter-process communication is relatively complex and usually needs to be implemented through inter-process communication mechanisms (IPC, Inter-Process Communication), such as message queues, semaphores, pipes, etc.
  • Creation: The creation of a process is usually time-consuming, and the new process has its own address space.

Thread:

  • Definition: A thread is an execution unit in a process and is part of the process. A process can contain multiple threads, which share the process’s address space and resources.
  • Resource Sharing: Threads share the same address space and file descriptors, and communication between them is relatively easy.
  • Switching cost: The cost of thread switching is relatively low because threads share the same address space and only a small amount of context information such as registers needs to be saved and restored when switching.
  • Communication: Communication between threads is relatively easy because they share the same memory space. But you also need to pay attention to synchronization and mutual exclusion to prevent problems such as data competition.
  • Creation: Thread creation is usually lightweight and fast.

Difference:

  • Resource independence: Processes have independent memory spaces, while threads share the same memory space.
  • Switching cost: The cost of process switching is high, while the cost of thread switching is relatively low.
  • Communication: Inter-process communication is relatively complex, while thread communication is relatively easy.
  • Creation: Process creation is expensive, and thread creation is relatively lightweight.
  • Robustness: Since processes have independent memory spaces, the crash of one process will not directly affect other processes. In multi-threading, problems with one thread can cause the entire process to crash.

2. Thread synchronization

Thread synchronization refers to a coordination mechanism adopted by multiple threads when accessing shared resources to ensure that access to shared resources is orderly and safe. In a multi-threaded environment, if there is no appropriate synchronization mechanism, problems such as race conditions, deadlocks, and data inconsistencies may occur. The following are some common thread synchronization mechanisms:
Mutex: Mutex is one of the most basic synchronization mechanisms. Only one thread is allowed to hold the mutex lock at a time, and other threads must wait for the lock to be released. This ensures exclusive access to shared resources.
Semaphore: Semaphore is a more general synchronization mechanism that allows multiple threads to access critical sections at the same time. The semaphore maintains a counter indicating the number of threads that can access it simultaneously.
Condition Variable: Condition variables allow threads to wait when a certain condition occurs or is met, thus avoiding busy waiting. It is often used with a mutex lock, where a thread waiting for a certain condition releases the lock and then enters a blocked state.
Read-Write Lock: Read-Write Lock allows multiple threads to read shared resources at the same time, but requires mutual exclusion during write operations. This improves read performance because multiple threads can read simultaneously, but writes are still mutually exclusive.
Atomic operation: An atomic operation is an indivisible operation that is guaranteed not to be interrupted by other threads during execution. Some modern programming languages and libraries provide atomic operations to ensure that operations on shared data are atomic.
Barrier: A barrier is used to ensure that all threads reach a certain point before they can continue execution. It is often used to synchronize the execution order of multiple threads.
These synchronization mechanisms can be selected and combined according to specific application scenarios and needs. The correct use of thread synchronization mechanism can effectively avoid problems in concurrent environments and ensure the correctness and stability of multi-threaded programs. However, incorrect synchronization can lead to problems that are difficult to debug and fix, so the selection and use of synchronization mechanisms need to be carefully considered when designing and implementing multi-threaded programs.

3. Multi-threading

Multithreading is a multi-tasking concurrent working method. In Linux, threads include kernel threads and user threads. Kernel threads are managed by the kernel and do not require us to do more work. What we are talking about here are user threads. Threads are unified by users. thread to switch.

The advantages of multithreading include:
Concurrent execution: Multi-threading allows different parts of the program to be executed at the same time, improving the concurrency of the program.
Resource Sharing: Threads share the same address space and resources, simplifying data sharing and communication.
Responsiveness: Multi-threading can improve the responsiveness of the system because the blocking of one thread will not affect the execution of other threads.
Task decomposition: Complex tasks can be decomposed into multiple threads to improve the structure and maintainability of the program.
Parallel processing: On multi-core processors, multi-threading can achieve true parallel processing and make full use of hardware resources.

However, multi-threaded programming also brings some challenges, such as:
Synchronization and mutual exclusion: Sharing resources between multiple threads may lead to race conditions, requiring the use of synchronization and mutual exclusion mechanisms to ensure data consistency.
Deadlock: Improper synchronization can lead to deadlock, making it impossible for a thread to continue executing.
Difficulty in Debugging: Debugging multi-threaded programs is relatively complex because there are multiple execution flows.
Performance overhead: Thread creation and switching have certain performance overhead.

4. Thread related functions

int pthread_create(pthread_t id,pthread_attr_t *attr, void *(*start_runtine)(void *), void *arg);//Thread creation function
Get the thread ID (that is, the pthread_t id created above): pthread_t pthread_self();
Exit the thread: void pthread_exit(void *retval);
Suspended thread: int pthread_join(pthread_t id,void **return);
Thread synchronization: There are two ways to provide thread synchronization in POSIX, condition variables and mutex locks

Mutex lock:

pthread_mutex_t *mutex;//mutex lock variable
int pthread_mutex_init(pthread_mutex_t *mutex, pthread_attr_t *attr);//Initialize a mutex lock
int pthread_mutex_lock(pthread_mutex_t *mutex);//Lock the mutex, so that when one thread locks, the other thread will be in a waiting state
int pthread_mutex_unlock(pthread_mutex_t *mutex);//Unlock the mutex lock. If unlocked, the waiting thread will have the opportunity to access the critical section.

Condition variable: It is actually a supplement to the mutex lock, because the thread can be unlocked at the same time while waiting for the condition variable, which can be reflected in the producer and consumer modes.

pthread_cond_t cond;
int pthread_cond_init(pthread_cond_t *cond, const pthread_cond_addr *attr);//Initialize a condition variable, and the following parameter attr is the attribute of the condition variable
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);//Release the mutex mutex and wait for the condition variable cond
int pthread_cond_timewait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);//Release the mutex mutex and wait for the condition variable cond. Unlike the pthread_cond_wait function, this function can ensure that the thread does not block within the abstime time .
int pthread_cond_signal(pthread_cond_t *cond);//Release condition variable
int pthread_cond_broadcast(pthread_cond_t *cond);//Release all threads blocked by cond, be careful when using this

5. Thread attributes

Before using these attributes, the relevant initialization function pthread_xxx_init(xxx *) must be called;
Thread attribute: pthread_attr_t

Most of the above related attributes, POSIX provides corresponding interfaces to operate. To set up a scheduling strategy:
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
int pthread_attr_init(pthread_attr_t *attr);//Initialize the thread attribute object
int pthread_attr_destroy(pthread_attr_t *attr);//Destroy the thread attribute object

Experimental equipment and software environment

Installation environment: divided into software environment and hardware environment
Hardware environment: An x86 architecture host with ddr3 4G memory and above
System environment: windows, linux or mac os x
Software environment: run vmware or virtualbox
Software environment: Ubuntu operating system

Experimental content

Topic requirements

In the Linux environment, use multi-threading and synchronization methods to write a program to simulate the train ticketing system. There are 3 windows in total and 10 tickets are sold. The program output results are similar (the program output is not unique and can be other similar results)
That is, there are three requirements:
1. Create three threads
2. Use mutex locks to ensure thread safety
3. Stop selling tickets when the ticket number is 0

My thoughts

Create a thread by using the pthread_create(pthread_t *tidp,const pthread_attr_t *attr,void *(start_rtn)(void),void *arg); function, where the parameters
The first parameter is a pointer to the thread identifier.
The second parameter is used to set thread properties.
The third parameter is the starting address of the thread running function.
The last parameter is the parameter to run the function.
The thread running function is the buyTicket() function written by myself. Its function is to sell tickets. When the ticket is 0, it stops selling tickets. Use pthread_mutex_lock(&mutex) inside the function to lock the mutex. The thread calls this function to lock the mutex. If the mutex is already locked and owned by another thread, the calling thread will block until the mutex becomes available (this can prevent multiple people from buying tickets) The votes were counted incorrectly).
At the same time, in order to prevent the ticketing function from being unlocked when the ticketing is completed, the CPU time slice allocated to the thread has not yet ended, so the loop continues to lock and sell tickets. This back and forth results in only one thread selling tickets, and other threads Stuck at getting the lock and locking it. (i.e. to prevent only one window from selling out tickets) I add a sleep (usleep(1);) after unlocking.
Finally, wait for the end of the thread by using the pthread_join() function.

#include<stdio.h>
#include<unistd.h>
#include<pthread.h> //Header file of mutex lock
int tickets = 10; //Total number of tickets
pthread_mutex_t mutex; //Initialization of mutex lock in C language multi-threading
void *buyTicket(void *arg) {<!-- -->
const char* name = (char*)arg;
while(tickets>0) {<!-- -->
pthread_mutex_lock( & amp;mutex);
tickets--;
printf("[%s] window sold a ticket, and there are %d tickets left\\
",name,tickets);
pthread_mutex_unlock( & amp;mutex);

//Prevent only one window from selling out the tickets
usleep(1);
}
printf("%s quit!\\
",name);
pthread_exit((void*)0);
// return NULL;
}
int main() {<!-- -->
\t
pthread_mutex_init( & amp;mutex, NULL);
pthread_t t1,t2,t3;
//Create thread
pthread_create( & amp;t1, NULL, buyTicket, "thread 1");
pthread_create( & amp;t2, NULL, buyTicket, "thread 2");
pthread_create( & amp;t3, NULL, buyTicket, "thread 3");
//Wait for thread execution to end
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
//Unregister the mutex lock
pthread_mutex_destroy( & amp;mutex);
return 0;
}


We can see that this program successfully uses multi-threading and synchronization methods to implement a simulated train ticketing system. There are 3 windows in total, namely thread1, thread2 and thread3. A total of 10 tickets are sold, and the sale stops when the ticket is 0. ticket. At the same time, the use of mutex locks ensures thread safety.

Abnormal problems and solutions

abnormal:
After writing a program about threads, it cannot be compiled normally.
Solution:
Add #include to the header file (mutex header file)

abnormal:
“undefined reference to pthread_create’” appears when compiling

Solution:
Reason: pthread is not the default library under Linux, that is, when linking, the entry address of the function in the phread library cannot be found, so the link will fail.
Solution: When compiling with gcc, add the -lpthread parameter to solve the problem.

Successful operation result