linux_thread lock mutex (mutex)_thread synchronization_deadlock phenomenon_pthread_mutex_lock function_pthread_mutex_unlock function_deadlock phenomenon

Continuing from the previous article: linux_set thread attributes-pthread_attr_t structure-set thread separation state-modify thread stack size-NPTL

  Today I will share with you the knowledge of thread synchronization and thread lock in linux, because the competition between threads is disordered and chaotic, so we need a mechanism to ensure that our program achieves the effect we need. At this time, this kind of lock The mechanism was born, come on, let's take a look:

The catalog of articles published by this blogger on CSDN: My CSDN catalog, as a guide to the types of articles published by bloggers on CSDN

1. Thread synchronization

1.1. Thread synchronization

  Synchronization means coordinated steps, running in a predetermined order.
  Thread synchronization means that when a thread issues a function call, the call does not return until the result is obtained. At the same time, other threads cannot call this function to ensure data consistency.
  For example: 100 bytes in memory, thread T1 wants to fill all 1s, and thread T2 wants to fill all 0s. But if T1 executes 50 bytes and loses the CPU, T2 executes and overwrites the content written by T1. When T1 gets the cpu again, it continues to write 1 backwards from the position where it lost the cpu. When the execution ends, the 100 bytes in the memory are neither all 1s nor all 0s.
  The resulting phenomenon is called "time related error". To avoid this data clutter, threads need to be synchronized.
  The purpose of "synchronization" is to avoid data confusion and resolve time-related errors. In fact, not only synchronization is required between threads, but also synchronization mechanisms are required between processes, signals, and so on.
  Therefore, all situations of "multiple control flows operating together on a shared resource" require synchronization.

1.1.1. Example - thread data confusion

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
//线程之间共享资源标准输出
//线程回调函数
void *tfn(void *arg)
{
    
    
    srand(time(NULL));//设置时间种子
	while (1) 
	{
    
    
        printf("hello ");
        sleep(rand() % 3);	//模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误
        printf("world\n");
        sleep(rand() % 3);
    }
    return NULL;
}

int main(void)
{
    
    
    pthread_t tid;
    srand(time(NULL));//设置时间种子
    pthread_create(&tid, NULL, tfn, NULL);//创建线程
	while (1) 
	{
    
    
        printf("HELLO ");
        sleep(rand() % 3);//模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误
        printf("WORLD\n");
        sleep(rand() % 3);
    }
    pthread_join(tid, NULL);
    return 0;                           //main中的return可以将整个进程退出
}

  The expected output of the code is that the child thread outputs lowercase hello world, and the main thread outputs uppercase HELLO WORLD, but the result is that the data is output in disorder.
  The reason is: no synchronization mechanism is added due to sharing and competition, resulting in time-related errors and data confusion

1.2. Reasons for data confusion

  1. Resource sharing (not exclusive resources)
  2. Random scheduling (meaning competition for data access)
  3. Lack of necessary synchronization mechanism between threads.
  Among the above three points, the first two points cannot be changed. To improve efficiency and transfer data, resources must be shared. As long as resources are shared, there is bound to be competition. Data can easily become cluttered whenever there is competition.
  So we can only solve it from the third point. Mutual exclusion occurs when multiple threads access shared resources.

2. Mutex mutex------thread lock

  Linux provides a mutex mutex (also known as a mutex ).
  Each thread tries to lock the resource before operating on it. Only after successful locking can it operate, and the operation is completed and unlocked.
  Resources are still shared, and there is still competition between threads,
  but the access to resources is turned into a mutually exclusive operation through "locks", and then time-related errors will no longer occur.
  However, it should be noted that at the same time, only one thread can hold the lock.
  When A thread locks and accesses a global variable, B tries to lock it before accessing it, but if the lock cannot be obtained, B blocks. The C thread does not lock, but directly accesses the global variable, which can still be accessed, but data confusion will occur.
  Therefore, the mutex is essentially a "suggestion lock" (also known as "cooperative lock") provided by the operating system. It is recommended to use this mechanism when multiple threads in the program access shared resources. However, there is no mandatory limit.
  Therefore, even with a mutex, if a thread does not follow the rules to access data, it will still cause data confusion.

2.1. Thread lock related functions

  pthread_mutex_init function,
  pthread_mutex_destroy function,
  pthread_mutex_lock function,
  pthread_mutex_trylock function,
  pthread_mutex_unlock function
, the return values ​​of the above five functions are: success returns 0, failure returns an error number.
  The pthread_mutex_t type is essentially a structure. In order to simplify understanding, the implementation details can be ignored during application, and simply treated as integers.
  pthread_mutex_t mutex; The variable mutex has only two values ​​of 1 and 0.

2.1.1.pthread_mutex_init function

Function function:
  initialize a mutex (mutex), the initial value can be regarded as 1.
Header file:
  #include <pthread.h>
Function prototype:
  int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
Function parameters:
  mutex: the lock that needs to be initialized.
  The restrict keyword: it is only used to restrict pointers, telling the compiler that all operations that modify the contents of the pointer pointed to in memory can only be done through this pointer.
  The attr:mutex attribute cannot be modified by other variables or pointers than this pointer . It is an incoming parameter, usually NULL is passed, and the default attribute (shared between threads) is selected. Refer to APUE.12.4 Synchronization Attributes
    1. Static initialization: If the mutex mutex is statically allocated (defined globally, or modified with the static keyword), you can directly use macros for initialization. For example: pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;
    2. Dynamic initialization: local variables should be initialized dynamically. For example: pthread_mutex_init(&mutex, NULL)
return value:
  success: return 0;
  failure: return non-zero.
Note:
  After the function is successfully executed, the mutex is initialized to an unlocked state.

2.1.2.pthread_mutex_destroy function

Function role:
  destroy a mutex.
Header file:
  #include <pthread.h>
Function prototype:
  int pthread_mutex_destroy(pthread_mutex_t *mutex);
Function parameters:
  mutex: the lock that needs to be destroyed.
Return value:
  success: return 0;
  failure: return non-zero.

2.1.3.pthread_mutex_lock function

Function role:
  lock. It can be understood as mutex–(or -1)
header file:
  #include <pthread.h>
Function prototype:
  int pthread_mutex_lock(pthread_mutex_t *mutex);
Function parameters:
  mutex: The amount that needs to be locked.
Return value:
  success: return 0;
  failure: return non-zero.

2.1.4.pthread_mutex_unlock function

Function role:
  unlock. It can be understood as mutex ++ (or +1)
header file:
  #include <pthread.h>
Function prototype:
  int pthread_mutex_unlock(pthread_mutex_t *mutex);
Function parameters:
  mutex: The amount that needs to be unlocked.
Return value:
  success: return 0;
  failure: return non-zero.

2.1.5.pthread_mutex_trylock function

Function role:
  try to lock.
Header file:
  #include <pthread.h>
Function prototype:
  int pthread_mutex_trylock(pthread_mutex_t *mutex);
Function parameters:
  mutex: the amount to be locked.
Return value:
  success: return 0;
  failure: return non-zero.

2.2. Locking and unlocking

2.2.1. lock and unlock:

  lock tries to lock. If the lock is unsuccessful, the thread blocks until other threads holding the mutex are unlocked.
  unlock actively unlocks the function, and wakes up all threads blocked on the lock at the same time. As for which thread is woken up first, it depends on the priority and scheduling. Default: block first, wake up first.
  For example: T1 T2 T3 T4 uses a mutex lock. T1 is successfully locked, and other threads are blocked until T1 is unlocked. After T1 is unlocked, T2, T3, and T4 are all woken up, and automatically try to lock again.
  It can be assumed that the initial value of the mutex lock init is 1. The lock function is to set the mutex–. unlock will mutex++

2.2.2.lock and trylock:

  If the lock lock fails, it will block, waiting for the lock to be released.
  If trylock fails to lock, an error number (such as: EBUSY) is returned directly without blocking.

2.3. Modification 1.1.1, lock shared resources

Steps:
  1. Define the global mutex, initialize the init(&m, NULL) mutex, and add the corresponding destry
  2. In the two threads while, before and after the two printfs, add lock and unlock respectively
  3. Move the unlock to the second After two sleeps, it is found that the alternation phenomenon is difficult to appear.
    The thread should be unlocked immediately after operating the shared resource, but after the modification, the thread sleeps holding the lock. After waking up and unlocking, it will be locked immediately, and these two library functions will not block themselves.
    So the probability of losing cpu between these two lines of code is very small. Therefore, it is difficult for another thread to get the opportunity to lock.

  4. Add n=10 to main and add n to while -- At this time, the main thread exits after outputting 10 times, calls pthread_cancel() to exit the child thread, and destroys the lock.
code show as below:

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
pthread_mutex_t mutex;
//线程之间共享资源标准输出
//线程回调函数
void *tfn(void *arg)
{
    
    
    srand(time(NULL));//随机数时间种子
while (1) 
{
    
    
        pthread_mutex_lock(&mutex);//对标准输出加锁
        printf("hello ");
        sleep(rand() % 3);	//模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误
        printf("world\n");
        pthread_mutex_unlock(&mutex);//解锁
        sleep(rand() % 3);
    }
    return NULL;
}
int main()
{
    
    
int n = 10;
    pthread_t tid;
    srand(time(NULL));//设置随机数时间种子
    pthread_mutex_init(&mutex, NULL);//初始化锁
    pthread_create(&tid, NULL, tfn, NULL);//创建线程
while (n--) 
{
    
    
        pthread_mutex_lock(&mutex);//对共享资源加锁
        printf("HELLO ");
        sleep(rand() % 3);
        printf("WORLD\n");
        pthread_mutex_unlock(&mutex);//解锁
        sleep(rand() % 3);
}
pthread_cancel(tid);                //  将子线程杀死,子线程中自带取消点
    pthread_join(tid, NULL);//等待线程退出,当然,因为是死循环,子线程不会退出,可自行设置退出的方法。
    pthread_mutex_destroy(&mutex);//销毁锁
    return 0;                           //main中的return可以将整个进程退出
}

Conclusion:
  lock before accessing shared resources, and unlock immediately after accessing. The "granularity" of the lock should be as small as possible.

2.4. Deadlock phenomenon

For example:
  1. The thread tries to lock the same mutex A twice.
  2. Thread 1 owns lock A and requests lock B; thread 2 owns lock B and requests lock A

code show as below:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
int a= 50, b = 600;
pthread_mutex_t m_a, m_b;//定义锁
//线程回调函数
void *tfn(void *arg)
{
    
    
    int i = (int)arg;
	if (i == 1)
	{
    
    
        pthread_mutex_lock(&m_a);//线程1拿着a锁
        a = 55;
        sleep(1);       //给另外一个线程加锁,创造机会.
        pthread_mutex_lock(&m_b);//请求b锁
        b = 666; 
        pthread_mutex_unlock(&m_a);
        pthread_mutex_unlock(&m_b);
        printf("----thread %d finish\n", i);
        pthread_exit(NULL);

    } 
    else if (i == 2) 
    {
    
    

        pthread_mutex_lock(&m_b);//线程2拿着b锁
        b = 44;
        sleep(1);
        pthread_mutex_lock(&m_a);//请求a锁
        a = 88; 
        pthread_mutex_unlock(&m_b);
        pthread_mutex_unlock(&m_b);
        printf("----thread %d finish\n", i);
        pthread_exit(NULL);
    }

    return NULL;
}

int main(void)
{
    
    
    pthread_t tid1, tid2;
    int ret1, ret2;
	//初始化锁
    pthread_mutex_init(&m_a, NULL);
    pthread_mutex_init(&m_b, NULL);
	//创建线程
    pthread_create(&tid1, NULL, tfn, (void *)1);
    pthread_create(&tid2, NULL, tfn, (void *)2);
    sleep(3);
    printf("var = %d, num = %d\n", a, b);

    ret1 = pthread_mutex_destroy(&m_a);      //释放琐
    ret2 = pthread_mutex_destroy(&m_b);
    if (ret1 == 0 && ret2 == 0) 
        printf("------------destroy mutex finish\n");

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    printf("------------join thread finish\n");

    return 0;
}

  The above is the sharing of this time, I hope it will be helpful to everyone, welcome to follow the blogger to learn more new knowledge together!

Guess you like

Origin blog.csdn.net/qq_44177918/article/details/130457076