In the previous thread learning, the locks used are all pending and waiting locks. If the lock cannot be applied for, it will wait in the lock;
Spinlocks are not so similar
Article directory
- 1. Spin lock
-
- 1.1 Concept
- 1.2 Interface
-
- 1.2.1 pthread_spin_init/destroy
- 1.2.2 pthread_spin_lock
- 1.2.3 pthread_spin_unlock
- 2. Read-write lock
-
- 2.1 The relationship between readers and writers
- 2.2 Interface
-
- 2.2.1 init/destroy
- 2.2.2 Reader locking
- 2.2.3 Writer lock
- 2.2.4 Set the properties of the lock
- 2.3 Code
1. Spinlock
1.1 Concept
The spin lock is a polling detection lock. Its detection mechanism is not to hang and wait, but to continuously ask whether the lock is free; similar to a trylock( )
Because it requires constant polling and detection, it will occupy a certain amount of CPU resources; if there are many threads, it will easily cause a load on the CPU.
However, the spin lock does not need to wake up the thread in the suspended waiting
state, and its consumption is relatively small.
in conclusion:
- Spin locks are suitable for situations where the competition is not fierce and the critical section is small (short stay time)
- Spin locks are not suitable for a large number of threads and long critical sections
The advantages and disadvantages of spin locks, in turn, are the advantages and disadvantages of hanging and waiting for locks. We need to choose the correct type of lock according to different scenarios
1.2 interface
The related interface is very similar to mutex
, so the effect of using it will not be demonstrated here
1.2.1 pthread_spin_init/destroy
#include <pthread.h> int pthread_spin_destroy(pthread_spinlock_t *lock); int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
1.2.2 pthread_spin_lock
The spin lock also has a trylock
interface, which is used to determine whether the lock is ready
#include <pthread.h> int pthread_spin_lock(pthread_spinlock_t *lock); int pthread_spin_trylock(pthread_spinlock_t *lock);
1.2.3 pthread_spin_unlock
#include <pthread.h> int pthread_spin_unlock(pthread_spinlock_t *lock);
2. Read-write lock
Sometimes, we will have a config
configuration file, which will be read by many threads, but rarely modified and written.
At this time, if we lock the reading of the configuration file, it will easily lead to efficiency problems, and many threads will be blocked continuously, resulting in performance loss.
At this point, you can use a dedicated read-write lock to add different locks to readers and writers, so as to improve read performance while ensuring that writes do not conflict
2.1 The relationship between readers and writers
Needless to say, there must be a mutually exclusive relationship between writing and writing;
Readers and writers also have a mutually exclusive relationship. When writing, they cannot be read, otherwise ambiguity problems will easily occur;
- The writer writes a, thread A reads it, and the result is a
- The writer continues to write b, and thread B reads it, and the result is ab
This is because the writing of the writer has not been completed, so readers A and B will get completely different results, which is wrong;
There is no relationship between readers and readers, because readers do not modify data, nor take data away, and their existence has no impact on critical resources.
2.2 Interface
2.2.1 init/destroy
Read-write locks only need to initialize one lock, and there is no need to initialize two different locks for readers and writers
#include <pthread.h> int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
2.2.2 Reader lock
The reader lock/writer lock of the read-write lock is separate, and we need to call different locks for different threads
#include <pthread.h> int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
But the unlocked interface is the same
#include <pthread.h> int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
2.2.3 Writer lock
#include <pthread.h> int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
2.2.4 Set the properties of the lock
See PTHREAD_RWLOCKATTR_SETKIND_NP – Linux man page
Read-write locks allow us to set whether readers are preferred or writers are preferred. If the default attributes are used, there may be a situation where the reader has been reading and the writer has no way to write (printing misalignment is a normal situation)
[muxue@bt-7274:~/git/linux/code/23-01-20 rwlock]$ ./test reader [140484801697536] reader [140484801697536] 0 reader [140484793304832] reader [reader [140484784912128140484793304832] reader [140484784912128] 0 ] 0 reader [140484759734016] reader [140484759734016] 0 reader [140484776519424]
At this time, the writer starvation
problem occurs. The writer cannot access critical resources and starves to death
We can set the properties of the read-write lock according to our own needs
int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref); /* There are 3 options in pref PTHREAD_RWLOCK_PREFER_READER_NP (default setting) Reader priority, may lead to writer starvation PTHREAD_RWLOCK_PREFER_WRITER_NP Writer priority, currently has a BUG, resulting in performance behavior and PTHREAD_RWLOCK_PREFER_READER_NP consistent PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP The writer has priority, but the writer cannot lock recursively to avoid deadlock */
Here is an example
pthread_rwlock_t rwlock;//lock pthread_rwlockattr_t attr;//Attribute pthread_rwlockattr_init( & amp;attr);//initialization attribute pthread_rwlockattr_setkind_np( & amp;attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);//Set the attribute of the lock to the writer priority pthread_rwlock_init( & amp;rwlock, & amp;attr);//Initialize and set the attributes of the read-write lock
2.3 Code
#include <iostream> #include <unistd.h> #include <pthread.h> using namespace std; volatile int board = 0;//critical resource pthread_rwlock_t rw;//global read-write lock void *reader(void* args) {<!-- --> const char *name = static_cast<const char *>(args); cout << "reader ["<<pthread_self() <<"]"<< endl; //sleep(1); while(true) {<!-- --> pthread_rwlock_rdlock( & rw); cout << "reader ["<<pthread_self() <<"] " << board << endl; pthread_rwlock_unlock( & rw); usleep(110); } } void *writer(void *args) {<!-- --> const char *name = static_cast<const char *>(args); //sleep(1); while(true) {<!-- --> pthread_rwlock_wrlock( & rw); board + + ; cout << "writer [" << pthread_self() <<"]"<< endl; pthread_rwlock_unlock( & rw); usleep(100); } } int main() {<!-- --> pthread_rwlock_init( &rw, nullptr); pthread_t r1, r2, r3, r4, r5, r6, w1, w2; pthread_create( & amp;r1, nullptr, reader, (void*)"reader"); pthread_create( &r2, nullptr, reader, (void*)"reader"); pthread_create( &r3, nullptr, reader, (void*)"reader"); pthread_create( &r4, nullptr, reader, (void*)"reader"); pthread_create( &r5, nullptr, reader, (void*)"reader"); pthread_create( &r6, nullptr, reader, (void*)"reader"); pthread_create( &w1, nullptr, writer, (void*)"writer"); pthread_create( & amp;w2, nullptr, writer, (void*)"writer"); pthread_join(r1, nullptr); pthread_join(r2, nullptr); pthread_join(r3, nullptr); pthread_join(r4, nullptr); pthread_join(r5, nullptr); pthread_join(r6, nullptr); pthread_join(w1, nullptr); pthread_join(w2, nullptr); pthread_rwlock_destroy( & rw); return 0; }
The result of the operation is as follows
reader [140067523458816] 7086 reader [140067548636928] 7086 writer [140067515066112] writer [140067506673408] reader [140067540244224] 7088 reader [140067565422336] 7088 reader [140067557029632] 7088 reader [140067531851520] 7088 reader [140067523458816] 7088 writer [140067515066112] writer [140067506673408] reader [140067548636928] 7090 writer [140067515066112] reader [140067523458816] 7091 writer [140067506673408] reader [140067548636928] 7092 reader [140067557029632] 7092
You can see that the reader has many threads and can read the data correctly.
If you add sleep(10)
to the reader’s while
void *reader(void* args) {<!-- --> const char *name = static_cast<const char *>(args); cout << "reader ["<<pthread_self() <<"]"<< endl; while(true) {<!-- --> pthread_rwlock_rdlock( & rw); cout << "reader ["<<pthread_self() <<"] " << board << endl; sleep(10);//sleep pthread_rwlock_unlock( & rw); usleep(110);//Avoid only one thread working } }
It can be seen that there is no conflict between multiple readers, and there will be no situation where reader B cannot access the critical section after reader A applies for a lock. If it is a mutual exclusion lock, reader A goes to sleep after applying, and B cannot apply for the lock.