Locking is to avoid unpredictable errors when multithreading or multiprocessing operate critical resources and ensure that the program executes in the expected order. There are many kinds of locks, several of which are introduced here.
1. Mutex
mutex is a lock when a process or thread locks after entering the critical area, and other processes or threads cannot enter the critical area before unlocking. The implementation method is as follows:
pthread_mutex_t mutex; pthread_mutex_init(&mutex, NULL); pthread_mutex_lock(&mutex); (*pcount) ++; //Simulated operational critical resources pthread_mutex_unlock(&mutex);
It is worth noting that when the mutex is locked, it will cause the waiting thread to sleep, that is, thread switching. Thread switching requires mv saving the value of the current register and the relevant attributes of the operating system (file system related, virtual memory, etc.), especially the resource switching cost related to the operating system. Therefore, There is a way to try locking. Try it. If you are locked, continue to do other things instead of switching threads. In this way, you can realize the scheduling method in the application layer, avoiding the system switching threads.
if (0 != pthread_mutex_trylock(&mutex)) { i --; continue; } (*pcount) ++; thread_mutex_unlock(&mutex);
2. Spin lock
Spinlock spinlock is similar to mutex lock. The difference is that the mutex will switch threads when encountering locked resources, while the spinlock will wait circularly until the lock is released. After locking, the CPU will wait all the time and will not switch threads even when the time slice arrives.
pthread_spinlock_t spinlock; pthread_spin_init(&spinlock, PTHREAD_PROCESS_SHARED); pthread_spin_lock(&spinlock); (*pcount) ++; pthread_spin_unlock(&spinlock);
Here again, when to use a mutex lock and when to use a spin lock? The boundary is whether the operation of the critical area is complicated. The standard is that compared with the operation of thread switching, mutex lock is used if it is more complex than thread switching, and spin lock is used if it is simpler than thread switching. Of course, it's best to use trylock() when using mutexes.
3. Read write lock
rdlock is not recommended because it is troublesome to write code. It can only be used when there is more reading and less writing and it is clearly known that the performance of mutex lock is not as good as that of read-write lock. The main feature is that multiple threads are allowed to read at the same time, but multiple threads are not allowed to write or read and write at the same time.
pthread_rwlock_t rwlock; pthread_rwlock_init(&rwlock, NULL); pthread_rwlock_rdlock(&rwlock); printf("count --> %d\n", count); pthread_rwlock_unlock(&rwlock);
Other threads
pthread_rwlock_wrlock(&rwlock); (*pcount) ++; pthread_rwlock_unlock(&rwlock);
Finally, no matter what kind of lock you use, you should pay attention to avoid deadlock.
4. Atomic operation
Here, atomic operations can also achieve the effect of locking, because other threads cannot operate the critical area after the operation is executed in one breath.
int inc(int *value, int add) { int old; __asm__ volatile ( "lock; xaddl %2, %1;" : "=a" (old) : "m" (*value), "a" (add) : "cc", "memory" ); return old; }
Assembly code is used here. Note that because the assemblers of different operating systems are different, the syntax of assembly language will be different. If linux is at & T instruction set and windows is x86 instruction set, mov instruction will be different.
Let's talk about CAS again. CAS is also an atomic operation. The command is xchg(), which can be used in thread safe singleton mode.
xchg(instance, NULL, newobj);
5. Shared memory
The above methods are for multithreading. If it is multi process locking, you need to use shared memory. Take memory mapping as an example.
int *pcount = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_ANON|MAP_SHARED, -1, 0); int i = 0; pid_t pid = 0; for (i = 0;i < THREAD_SIZE;i ++) { pid = fork(); if (pid <= 0) { usleep(1); break; } } if (pid > 0) { for (i = 0;i < 100;i ++) { printf("count --> %d\n", (*pcount)); sleep(1); } } else { int i = 0; while (i++ < 100000) { inc(pcount, 1); usleep(1); } }
The anonymous mapping used here can only be used between processes with parent-child relationship. If you open an fd to map a file, it is not limited to processes with parent-child relationship.
Here's another hint. If multiple processes share a memory pool, you can also use the method of shared memory.
Finally, let's talk about volatile. Volatile reminds the compiler that the variables defined behind it may change at any time, so every time the compiled program needs to store or read this variable, it will directly read data from the variable address. If there is no volatile keyword, the compiler may optimize reading and storage, and may temporarily use the value in the register. If this variable is updated by another program, there will be inconsistency. Simply put, it is to prevent the compiler from optimizing variables.