Thread lock for concurrent thread programming

There are many ways to achieve concurrency, including processes, threads, programming based on asynchronous event mechanisms, and so on. For multi-threaded programming, multiple threads in the same process should share the user address space of the process and resources such as pc. So there will be data competition, so it will involve thread synchronization mechanism (lock mechanism) or lock-free programming that depends on the atomic primitives of the instruction set provided by the cpu. And this section mainly describes the lock mechanism between Linux systems.  

 

1 Mutex

In the actual running process of threads, we often need multiple threads to maintain synchronization.

At this time, mutex locks can be used to complete the task. In the use of mutex locks, there are mainly

pthread_mutex_init、pthread_mutex_destory、pthread_mutex_lock、pthread_mutex_unlock

These functions are used to complete lock initialization, lock destruction, lock and lock release operations.

1.1 The creation of the lock

The lock can be created dynamically or statically. The macro PTHREAD_MUTEX_INITIALIZER can be used to initialize the lock statically. It is easier to understand in this way. The mutex is a structure of pthread_mutex_t, and this macro is a structural constant. The static initialization lock can be completed as follows :

pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER;

In addition, the lock can be dynamically created with the pthread_mutex_init function, the function prototype is as follows:

int pthread_mutex_init(pthread_mutex_t*mutex, const pthread_mutexattr_t * attr)

1.2 The properties of the lock

The mutex attribute can be initialized by pthread_mutexattr_init(pthread_mutexattr_t *mattr), and then other attribute setting methods can be called to set its attributes.

The scope of the mutex lock: you can specify whether the process is synchronized with other processes or between different threads in the same process. Can be set to PTHREAD_PROCESS_SHARE and PTHREAD_PROCESS_PRIVATE. The default is the latter, which means that the lock is used in the process. can use

int pthread_mutexattr_setpshared(pthread_mutexattr_t*mattr, int pshared)

pthread_mutexattr_getpshared(pthread_mutexattr_t*mattr,int *pshared)

Used to set and acquire the range of locks;

Types of mutex locks: There are the following value spaces:

PTHREAD_MUTEX_TIMED_NP, this is the default value, which is a normal lock . When a thread locks, the remaining threads requesting the lock will form a waiting queue, and obtain the lock according to the priority after unlocking. This lock strategy ensures the fairness of resource allocation. PTHREAD_MUTEX_RECURSIVE_NP, nested lock , allows the same thread to successfully acquire the same lock multiple times and unlock it through multiple unlocks. If it is a request from a different thread, it will compete again when the locking thread is unlocked. PTHREAD_MUTEX_ERRORCHECK_NP, error detection lock , if the same thread requests the same lock, it returns EDEADLK, otherwise the action is the same as the PTHREAD_MUTEX_TIMED_NP type. This ensures that the simplest case of deadlock will not occur when multiple locks are not allowed. PTHREAD_MUTEX_ADAPTIVE_NP, adaptive lock, the simplest type of lock, only waiting to be unlocked and then competing again.

可以用 pthread_mutexattr_settype(pthread_mutexattr_t *attr , int type) pthread_mutexattr_gettype(pthread_mutexattr_t *attr , int *type)

Get or set the type of lock.

1.3 Release of the lock

After calling pthread_mutex_destory, the resources occupied by the lock can be released, but there is a premise that the lock is currently not locked.

1.4 Lock operation

The operation of the lock mainly includes locking pthread_mutex_lock(), unlocking pthread_mutex_unlock() and testing and locking pthread_mutex_trylock().

int pthread_mutex_lock(pthread_mutex_t*mutex) int pthread_mutex_unlock(pthread_mutex_t *mutex) int pthread_mutex_trylock(pthread_mutex_t *mutex) The semantics of pthread_mutex_trylock() is similar to that of pthread_mutex_lock(), except that it returns while waiting for the EBUSY lock instead of being occupied.

1.5 Code explanation:

Code description 1: Basic application of mutex lock

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

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int count = 0;

void* consume(void *arg)
{
    while(1)
    {
        pthread_mutex_lock(&mutex);
        printf("************************consume begin lock\n");  
        printf("************************consumed %d\n",count);  
        count++;
        sleep(2);
        printf("************************consume over lock\n"); 
        pthread_mutex_unlock(&mutex); 
        printf("************************I'm out of pthread_mutex\n"); 
        sleep(1);
    }
    
    return NULL;
}

void* produce( void * arg )
{
    while(1)
    {
        pthread_mutex_lock(&mutex );
        printf("product begin lock\n");
        printf("produced %d\n", count);
        printf("product over lock\n");
        pthread_mutex_unlock(&mutex );
        printf("I'm out of pthread_mutex\n");
        sleep(1);
    }
    
    return NULL;
}

int main( void )
{
    pthread_t thread1,thread2;
    pthread_create(&thread1, NULL, &produce, NULL );
    pthread_create(&thread2, NULL, &consume, NULL );
    pthread_join(thread1,NULL);
    pthread_join(thread2,NULL);
    return 0;
}

 

Explanation of results:

[root@rocket lock-free]# g++ -g -o pthread_mutex_lockpthread_mutex_lock.cpp -lpthread

[root@rocket lock-free]#./pthread_mutex_lock

product begin lock

produced 0

product over lock

I'm out of pthread_mutex

************************ consume beginlock

************************consumed 0

/*Waiting for 2 seconds in the middle, but the product thread did not execute!*/

************************consume overlock

************************I'm out ofpthread_mutex

product begin lock

produced 1

product over lock

I'm out of pthread_mutex

product begin lock

produced 1

product over lock

I'm out of pthread_mutex

************************ consume beginlock

************************consumed 1

************************consume overlock

************************I'm out ofpthread_mutex

product begin lock

produced 2

product over lock

I'm out of pthread_mutex

************************ consume beginlock

************************consumed 2

************************consume overlock

************************I'm out ofpthread_mutex

Code description 2: Use of pthread_mutext_trylock

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

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int count = 0;

void* consume(void *arg)
{
    while(1)
    {
        pthread_mutex_lock(&mutex);
        printf("************************consume begin lock\n");  
        printf("************************consumed %d\n",count);  
        count++;
        sleep(2);
        printf("************************consume over lock\n"); 
        pthread_mutex_unlock(&mutex); 
        printf("************************I'm out of pthread_mutex\n"); 
        sleep(1);
    }
    
    return NULL;
}

void* produce( void * arg )
{
    while(1)
    {
        if(pthread_mutex_trylock(&mutex ) == 0)
        {
            printf("product begin lock\n");
            printf("produced %d\n", count );
            printf("product over lock\n");
            pthread_mutex_unlock(&mutex);
            printf("I'm out of pthread_mutex\n");
            sleep(1);
        }
        else
        {
            printf("I have try!But i can`t lock the mutex!\n");
            sleep(1);
        }
    }
    
    return NULL;
}

int main( void )
{
    pthread_t thread1,thread2;
    pthread_create(&thread1, NULL, &produce, NULL );
    pthread_create(&thread2, NULL, &consume, NULL );
    pthread_join(thread1,NULL);
    pthread_join(thread2,NULL);
    return 0;
}

 

Explanation of results:

[root@rocket lock-free]# g++ -g -o pthread_mutex_trylock pthread_mutex_trylock.cpp -lpthread

[root@rocket lock-free]#./pthread_mutex_trylock

************************ consume beginlock

************************consumed 0

/* Trylock returns without success! */

I have try!But i can`t lock the mutex!

I have try!But i can`t lock the mutex!

************************consume overlock

************************I'm out ofpthread_mutex

product begin lock

produced 1

product over lock

I'm out of pthread_mutex

************************ consume beginlock

************************consumed 1

I have try!But i can`t lock the mutex!

I have try!But i can`t lock the mutex!

************************consume overlock

************************I'm out ofpthread_mutex

product begin lock

produced 2

product over lock

I'm out of pthread_mutex

************************ consume beginlock

************************consumed 2

I have try!But i can`t lock the mutex!

I have try!But i can`t lock the mutex!

************************consume overlock

************************I'm out ofpthread_mutex

2 Read-write lock

The read-write lock has three states, so it can have higher parallelism.

2.1 Features

Only one thread can hold the read-write lock in write mode at a time, but multiple threads can hold the read-write lock in read mode at the same time. It is precisely because of this feature that when the read-write lock is in the write-locked state, the lock is unlocked Previously, all threads that tried to lock this lock would be blocked.

When the read-write lock is in the read-locked state, all threads that try to lock it in read mode can get access, but if the thread wants to lock the lock in write mode, it must block until all threads are released lock.

Generally, when the read-write lock is locked in the read mode, if another thread tries to lock in the write mode, the read-write lock will usually block subsequent read mode lock requests, which can prevent the read mode lock from being occupied for a long time and waiting The write mode lock request is blocked for a long time.

2.2 Applicability

The read-write lock is suitable for situations where the number of reads to the data structure is much greater than the number of writes. Because it can be shared when locked in read mode, it means exclusive when locked in write mode, so the read-write lock is also called shared-exclusive lock.

2.3 API initialization and destruction

#include <pthread.h> int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); returns 0 for success, and error number for errors

Like the mutex lock, before releasing the memory occupied by the read-write lock, you need to clean up the read-write lock through pthread_rwlock_destroy and release the resources allocated by init.

2.4 Read and write

#include <pthread.h> int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

These three functions implement the operations of acquiring a read lock, acquiring a write lock and releasing a lock. The two functions of acquiring a lock are blocking operations

Similarly, the non-blocking function is:

#include <pthread.h> int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

Non-blocking lock acquisition operation, if it can be acquired, it returns 0, otherwise it returns an error EBUSY

2.5 code explanation

Code description 1: Basic application of read-write lock

#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <bits/pthreadtypes.h>
 
static pthread_rwlock_t rwlock; //读写锁对象

int count = 0;

void *thread_function_read(void *arg)
{
    while(1)
    {
        pthread_rwlock_rdlock(&rwlock);
        printf("************************%d, read count %d\n", pthread_self(), count);
        sleep(1);
        pthread_rwlock_unlock(&rwlock);
	usleep(100);
    }
    
    return NULL;
}

void *thread_function_write(void *arg)
{
    while(1)
    {
        pthread_rwlock_wrlock(&rwlock);
        count++;
        printf("************************%d, write count %d\n", pthread_self(), count);
        sleep(5);
        pthread_rwlock_unlock(&rwlock);
	usleep(100);
    }
    return NULL;
}
   
int main(int argc, char *argv[])
{
    pthread_t rpthread1, rpthread2, wpthread;

    pthread_rwlock_init(&rwlock,NULL);

    pthread_create(&rpthread1, NULL, thread_function_read, NULL);
    pthread_create(&rpthread2, NULL, thread_function_read, NULL);
    pthread_create(&wpthread, NULL, thread_function_write, NULL);

    pthread_join(rpthread1, NULL);           
    pthread_join(rpthread2, NULL);           
    pthread_join(wpthread, NULL);           
               
    pthread_rwlock_destroy(&rwlock);          
    exit(EXIT_SUCCESS);
}

Explanation of results:

[root@rocket lock-free]#./pthread_rwlock

/* 2 reader threads do not block each other*/

************************1442944768,read count 0 

************************1432454912,read count 0

/* Write thread blocks all other threads*/

************************1421965056,write count 1

************************1442944768,read count 1

************************1432454912,read count 1

************************1421965056,write count 2

************************1442944768,read count 2

************************1432454912,read count 2

************************1421965056,write count 3

************************1442944768,read count 3

************************1432454912,read count 3

************************1421965056,write count 4

What is interesting is that if you add and remove usleep(100) in thread_function_read and thread_function_write in the above code, the following results will appear

[root@rocket lock-free]#./pthread_rwlock

************************-1896831232,read count 0

************************-1907321088,read count 0

************************-1907321088,read count 0

************************-1896831232,read count 0

************************-1907321088,read count 0

************************-1896831232,read count 0

************************-1907321088,read count 0

I found that the write lock could not be grabbed. According to my original understanding, because the reader thread is started first, the reader first grabs the lock. After the reader grabs the lock, the writer is blocked on the lock request. When the reader is released, it should be the writer's turn. Yeah, but it's not like this! When the reader requests the lock again after release, it can still be obtained! Writer can hardly get the lock!

Check the manual and write, "The pthread_rwlock_rdlock() function applies a read lock to the read-write lock referenced by rwlock. The calling thread acquires the readlock if a writer does not hold the lock and there are no writers blocked on thelock. It is unspecified Whether the calling thread acquires the lock when awriter does not hold the lock and there are writers waiting for the lock" means that when no writer is waiting for the write lock, the reader can get the read lock. However, there is no delineation. If a writer is expecting a write lock, what should I do?

Fortunately, Linux has the function pthread_rwlockattr_setkind_np.

enum

{

 PTHREAD_RWLOCK_PREFER_READER_NP,

 PTHREAD_RWLOCK_PREFER_WRITER_NP,

 PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP,

 PTHREAD_RWLOCK_DEFAULT_NP =PTHREAD_RWLOCK_PREFER_READER_NP

};

可是直接pthread_rwlockattr_setkind_np(&attr,PTHREAD_RWLOCK_PREFER_WRITER_NP);

It's useless! why? There is no man page, so I suspected that this function was not implemented, so I used debuginfo-install glibc? to install the glibc debugging symbols, and then followed up with gdb, and found that pthread_rwlockattr_setkind_np is indeed implemented. The code is very simple. Attr has been changed. A member variable of. Why is that?

Googling again, I finally found the man page of pthread_rwlockattr_setkind_np, with a note at the end, which made me sweat:

“Setting the value read-write lockkind to PTHREAD_RWLOCK_PREFER_WRITER_NP, results in the same behavior assetting the value to PTHREAD_RWLOCK_PREFER_READER_NP. As long as a readerthread holds the lock the thread holding a write lock will be starved. Settingthe kind value to PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP, allows thewriter to run. However, the writer may not be recursive as is implied by thename. “

It means,

PTHREAD_RWLOCK_PREFER_WRITER_NP and PTHREAD_RWLOCK_PREFER_READER_NP are the same! It should be set to PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP! But PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP also exists in name only, it will not be recursive.

So there is code description 2: The use of read-write lock priority

#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <bits/pthreadtypes.h>
 
static pthread_rwlock_t rwlock; //读写锁对象

int count = 0;

void *thread_function_read(void *arg)
{
    while(1)
    {
        pthread_rwlock_rdlock(&rwlock);
        printf("************************%d, read count %d\n", pthread_self(), count);
        sleep(1);
        pthread_rwlock_unlock(&rwlock);
	//usleep(100);
    }
    
    return NULL;
}

void *thread_function_write(void *arg)
{
    while(1)
    {
	pthread_rwlock_wrlock(&rwlock);
        count++;
        printf("************************%d, write count %d\n", pthread_self(), count);
        sleep(1);
        pthread_rwlock_unlock(&rwlock);
	usleep(100);
    }
    return NULL;
}
   
int main(int argc, char *argv[])
{
    pthread_t rpthread1, rpthread2, wpthread;
    
    pthread_rwlockattr_t attr;    
    pthread_rwlockattr_setkind_np(&attr,PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);
    pthread_rwlock_init(&rwlock, &attr);

    pthread_create(&rpthread1, NULL, thread_function_read, NULL);
    pthread_create(&rpthread2, NULL, thread_function_read, NULL);
    pthread_create(&wpthread, NULL, thread_function_write, NULL);

    pthread_join(rpthread1, NULL);           
    pthread_join(rpthread2, NULL);           
    pthread_join(wpthread, NULL);           
               
    pthread_rwlock_destroy(&rwlock);          
    exit(EXIT_SUCCESS);
}

 

operation result:

[root@rocket lock-free]#./pthread_rwlock_withpriority

************************1529054976,read count 0

************************1518565120,read count 0

************************1508075264,write count 1

************************1529054976,read count 1

************************1518565120,read count 1

************************1508075264,write count 2

************************1529054976,read count 2

************************1518565120,read count 2

************************1508075264,write count 3

This will not cause the writer to starve to death.

Code description 3: Use of pthread_rwlock_tryrdlock

#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <bits/pthreadtypes.h>
 
static pthread_rwlock_t rwlock; //读写锁对象

int count = 0;

void *thread_function_read(void *arg)
{
    int print_count = 0;
    while(1)
    {
        if (pthread_rwlock_tryrdlock(&rwlock) == 0)
	{
		printf("************************%d, read count %d\n", pthread_self(), count);
		sleep(1);
		pthread_rwlock_unlock(&rwlock);
		usleep(100);
	}
        else
	{
		print_count++;
		if (print_count % 10 == 0)
		{
			printf("I have try!But i can`t lock the rdlock!\n");
			print_count = 0;
		}
			
		usleep(100);
	}
    }
    
    return NULL;
}

void *thread_function_write(void *arg)
{
    while(1)
    {
        pthread_rwlock_wrlock(&rwlock);
        count++;
        printf("************************%d, write count %d\n", pthread_self(), count);
        sleep(5);
        pthread_rwlock_unlock(&rwlock);
	usleep(100);
    }
    return NULL;
}
   
int main(int argc, char *argv[])
{
    pthread_t rpthread1, rpthread2, wpthread;

    pthread_rwlock_init(&rwlock,NULL);

    pthread_create(&rpthread1, NULL, thread_function_read, NULL);
    pthread_create(&rpthread2, NULL, thread_function_read, NULL);
    pthread_create(&wpthread, NULL, thread_function_write, NULL);

    pthread_join(rpthread1, NULL);           
    pthread_join(rpthread2, NULL);           
    pthread_join(wpthread, NULL);           
               
    pthread_rwlock_destroy(&rwlock);          
    exit(EXIT_SUCCESS);
}

 

Explanation of results:

************************1819674368,read count 0

************************1809184512,read count 0

************************1798694656,write count 1

/* Trylock returns without success! */

I have try!But i can`t lock therdlock!

I have try!But i can`t lock therdlock!

I have try!But i can`t lock therdlock!

I have try!But i can`t lock therdlock!

************************1819674368,read count 1

************************1809184512,read count 1

************************1798694656, writecount 2

I have try!But i can`t lock therdlock!

I have try!But i can`t lock therdlock!

I have try!But i can`t lock therdlock!

I have try!But i can`t lock therdlock!

3 Spin lock

Spin lock is a low-level synchronization mechanism in the SMP architecture. When thread A wants to acquire a spin lock and the lock is held by another thread lock, thread A spins in a loop to check whether the lock is already available. For spin locks need to pay attention to:

Since the CPU is not released during spinning, the thread holding the spin lock should release the spin lock as soon as possible, otherwise the thread waiting for the spin lock will always spin there, which will waste CPU time.

The thread holding the spin lock should release the spin lock before sleep so that other threads can obtain the spin lock. (In kernel programming, if the code holding the spin lock sleeps, it may cause the entire system to hang)

The APIs related to Spin Lock operation provided by Pthreads mainly include:

intpthread_spin_destroy(pthread_spinlock_t *);

int pthread_spin_init(pthread_spinlock_t*, int);

intpthread_spin_lock(pthread_spinlock_t *);

intpthread_spin_trylock(pthread_spinlock_t *);

intpthread_spin_unlock(pthread_spinlock_t *);

3.1 Initialize the spin lock

pthread_spin_init is used to apply for the resources needed to use the spin lock and initialize it to a non-locked state. The value of pshared and its meaning:

PTHREAD_PROCESS_SHARED: The spin lock can be shared among threads in multiple processes.

PTHREAD_PROCESS_PRIVATE: Only threads in the process where the thread that initialized this spin lock is located can use the spin lock.

3.2 Obtain a spin lock

pthread_spin_lock is used to acquire (lock) the specified spin lock. If the spin lock is not currently held by other threads, the thread that calls the function obtains the spin lock. Otherwise, the function will not obtain the spin lock before return. If the thread calling the function already holds the spin lock when calling the function, the result is uncertain.

3.3 Attempt to acquire a spin lock

pthread_spin_trylock will try to acquire the specified spin lock, if it cannot be acquired, it will understand that it will return failure.

3.4 Release (unlock) a spin lock

pthread_spin_unlock is used to release the specified spin lock.

3.5 Destroy a spin lock

pthread_spin_destroy is used to destroy the specified spin lock and release all associated resources (the so-called all refers to the resources automatically applied for by pthread_spin_init). After calling this function, if pthread_spin_init is not called to reinitialize the spin lock, any attempt to use the The result of the lock call is undefined. If the spin lock is being used or the spin lock is not initialized when this function is called, the result is undefined.

4 Feature comparison

Lock type

Lock characteristics

Applicable scene

Mutex

Will cause thread switching

First choice in general

Read-write lock rwlock

Only one writer can have multiple readers at the same time

Read more and write less scene

Spinlock

Will not cause thread switching, which will increase CPU utilization. Suitable for small code segments

Small code snippets, scenarios where locking is not very frequent

Guess you like

Origin blog.csdn.net/smilejiasmile/article/details/114187192