当前位置:网站首页>AQS - detailed explanation of reentrantlock source code
AQS - detailed explanation of reentrantlock source code
2022-06-13 07:21:00 【Focus on writing bugs】
List of articles
What is? AQS
AQS
Is in Java in AbstractQueuedSynchronizer
Abbreviation . What he means is Abstract queue synchronizer
.
stay JUC(java.util.concurrent)
in , Many lock related operation classes , Will depend on him . For example, the following blog needs to say ReentrantLock
.
in the light of ReentrantLock
Simple use , You can refer to previous blogs java.util.concurrent.locks.Lock lock .
AQS What subclasses are there
stay java.util.concurrent.locks.AbstractQueuedSynchronizer
In this class , according to IDEA
It can generate its subclass information more intuitively . As shown below :【 Expand :】 About how to generate ?
Before interpreting the source code, you need to understand
1、 What is? CAS?
The content is more , Wrote an article independently , You can refer to the following address
CAS Understand the principle of
2、 What is fair lock ? What is unfair lock ?
Fair and unfair , Depends on when the lock is released by the previous thread , Whether the new thread can take the lock immediately .
1、 Let's start with T0、T1、T2 Need to get lock , Lock operation , But this time T0 Lock acquired successfully , that T1 and T2 You need to wait , wait for T0 After releasing the lock, obtain the lock .
2、 If another thread comes at this time T3, And this is the right time T0 Lock release successful ;
fair :T3 Line up backwards .
Unfair : You may not consider queuing , Directly participate in lock contention .
3、 About thread wakeup .
About thread sleep and wakeup , There are multiple ways to achieve .
sleep(xx)
sleep(long miilis) The method is Thread Class static methods , Called in code Thread.sleep(timeout) Method , Causes the operating system to suspend the current thread , call Thread.sleep(timeout) The following changes will occur after the method :- The state of the thread changes to TIMED_WAITING
- Called sleep After the method ,timeout Only when the thread is exhausted can it re-enter the executable state
- If in syncronized Code block Thread.sleep(timeout),Monitor The lock Owner There will be no switching , That is to say, the thread calls sleep Method does not release the lock
wait
And sleep The method is different ,wait It belongs to Object Class method ,JDK Two commonly used in wait Method , namely wait() and wait(long timeout). from Monitor It is not difficult for us to draw the following conclusion about the principle of lock :- wait Method must be in synchronized Code block ( Or used syncronized Methods of keyword modification )
- Thread calls wait Method will release the lock , Get into Monitor Lock object's WaitSet
- Thread calls wait The method will become Wating state
- Called wait() The thread of the method will always be in Waiting state , until Monitor Object's Owner Called notify perhaps notifyAll Method .notify Method will wake up randomly WaitSet One thread in , and notifyAll Will wake up WaitSet All threads in
- wait(long timeout) It will also wake up automatically when the waiting time is exhausted
park/unpark
park/unpark The function of is a bit similar to wait/notify, All pauses / Wake up the thread . He isjava.util.concurrent.locks.LockSupport
Next relatedStatic methods
.LockSupport.park(); LockSupport.unpark( Thread pointing ); // Wake up the specified thread
notify、notifyAll
notify/notifyAll() The method is Object The local final Method , Can't be rewritten .notify Wake up operation of , have
Randomness
.
【 mark :】
Above
Thread Sleep and wake up
Reference material :
Java Multithreading learning wait、notify/notifyAll Detailed explanation
java Concurrent learning - Thread sleep and wake up
ReentrantLock Source code interpretation
ReentrantLock Class structure
stay java.util.concurrent.locks.ReentrantLock
Source code ,ReentrantLock
Is defined as follows :
public class ReentrantLock implements Lock, java.io.Serializable
Inheritance from classes , It is not found whether it is related to AbstractQueuedSynchronizer(AQS)
Related . Actually ReentrantLock
The core of is its sync
Attribute information .
private final Sync sync;
ReentrantLock
Internal abstract class .
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/** * Performs {@link Lock#lock}. The main reason for subclassing * is to allow fast path for nonfair version. */
abstract void lock();
/** * Performs non-fair tryLock. tryAcquire is implemented in * subclasses, but both need nonfair try for trylock method. */
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
// Methods relayed from outer class
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
final boolean isLocked() {
return getState() != 0;
}
/** * Reconstitutes the instance from a stream (that is, deserializes it). */
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
It's about fair
and Unfair
In its Inner subclass
There are related definitions in .
// Unfair
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
// Lock mode
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
// fair
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/** * Fair version of tryAcquire. Don't grant access unless * recursive call or no waiters or is first. */
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
AbstractQueuedSynchronizer structure
Because in java.util.concurrent.locks.ReentrantLock
Define a Abstract inner classes java.util.concurrent.locks.ReentrantLock.Sync
, Where the inner class Realization AbstractQueuedSynchronizer
This abstract class
.
java.util.concurrent.locks.AbstractQueuedSynchronizer
The inheritance relationship of the class is as follows :
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable
As can be seen from the inheritance relationship ,java.util.concurrent.locks.AbstractQueuedSynchronizer
yes AbstractOwnableSynchronizer
Subclasses of , stay AbstractOwnableSynchronizer Parent class
in , only one java.util.concurrent.locks.AbstractOwnableSynchronizer#exclusiveOwnerThread
Declaration of attributes , And provided with get/set
Method .
Used to save the specific thread object reference .
stay AbstractQueuedSynchronizer Subclass
in , Defined as Node Inner class
, Used to save specific data information , Its structure is as follows :【 mark :】
AQS Mainly for the preservation of
Which thread holds the lock
This basic information .
Second, keepOthers have not been unlocked
The queue where the thread is locatedTeam leader
andA party
The direction of .
AQS The synchronization waiting queue in is also called CLH queue ,CLH The queue is Craig、Landin、Hagersten One invented by three people is based on Two way linked list data structure
Of queue
, yes FIFO First in, first out threads wait for the queue ,Java Medium CLH The original queue is CLH A variant of the queue , Thread from the original spin mechanism to block mechanism .
【 doubt :】 Why should it be designed as a two-way linked list ?
Bidirectional has the advantage of fast searching in different directions .
【 doubt :】 Two way linked list data Node Under the class thread What does the attribute point to ?
Lock not obtained , Thread object information that needs to be queued .
ReentrantLock Object creation
ReentrantLock There are two ways to create objects :
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
from Construction method
It can be seen that :
Take... Directly
new ReentrantLock()
By default, aUnfair NonfairSync object
.
Lock operation
With Fair lock
For example , Create a fair lock in the following ways :
Lock lock = new ReentrantLock(true);
Source code is as follows :
The locking operation is :
lock.lock();
see Fair lock
Of Lock method
, Its logic has the following definitions :
final void lock() {
acquire(1);
}
call acquire()
And pass on a Constant 1
.
Continue to explore down , The specific locking operation is as follows :
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
This code is integrated , Split it into code that is easy to identify , As shown below :
if (!tryAcquire(arg){
if(acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){
selfInterrupt();
}
}
Next, the logic implementation is analyzed :
tryAcquire: Attempt to acquire lock
In concretejava.util.concurrent.locks.ReentrantLock.FairSync
In subclass , There are the following ways totryAcquire
A copy of , The code is shown below :protected final boolean tryAcquire(int acquires) { // 1、 Get the object of the current execution thread final Thread current = Thread.currentThread(); // 2、 obtain AbstractQueuedSynchronizer in state The attribute value int c = getState(); // 3、 If the status information of the current state holder is 0 if (c == 0) { // 4、 by 0 This does not mean that no other thread is locked // You need to think about ,t0 The thread just released the lock , stay AbstractQueuedSynchronizer Whether there are other threads queued in the queue // !hasQueuedPredecessors() Indicates that there are no other threads in the queue to perform queueing operations // When judging AQS When there are no other threads queued in the queue , At this point, you try to obtain the lock // compareAndSetState take CAS Algorithm ( It's atomic ), To obtain the lock , And will AQS Medium state Make changes if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { // 5、 If there are no other threads in the queue waiting , And take CAS The algorithm obtains the lock successfully , Will AQS Medium exclusiveOwnerThread Set to reference of current thread // Indicates that the current thread holds a lock setExclusiveOwnerThread(current); return true; } } // 6、 Considering non 0 The situation of // Determine the current thread and AQS Stored in the exclusiveOwnerThread Whether the information matches // If the two match , It indicates that the lock is held by the current thread , So you can directly put AQS Medium state Conduct +1 operation . // Determine whether the current thread repeatedly locks ( Reentrancy ) else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); // modify AQS Medium state Status value information , Multiple calls lock() To lock , Accumulate them // Be careful :state Only for 0 The situation of , To allow other threads to acquire locks ! setState(nextc); return true; } // 7、 If the current lock holder is not the current thread , Then return to false, Indicates failed to acquire lock , Go to the next operation return false; }
if (c == 0)
situations , The processing operations are as follows :if (c != 0) And current == getExclusiveOwnerThread()
when , The processing operations are as follows :【 doubt :】 Why does it show up state Not for 0 The situation of ?
In the method, it may appear that each reference method also contains
lock.lock()
andlock.unlock()
Methods . But the method refers to other methods. On the whole, it belongs to the same thread repeatedly locking it . That is to sayLock reentry
.
As shown below :
At this point, execute before the business code , For the same threadtwo ( Or many times )
Lock operation .The code to judge whether there are other queues in the queue is as follows :
public final boolean hasQueuedPredecessors() { // The correctness of this depends on head being initialized // before tail and on head.next being accurate if the current // thread is first in queue. Node t = tail; // Read fields in reverse initialization order Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); // Queued AQS In the data structure , head head And tail tail All of them point to null when , // Means nothing else Node node , That is, there are no other waiting threads in the queue }
acquireQueued: Try to queue a thread object
When the program is executedtryAcquire(arg)
Return tofalse
when , ExpressCurrent thread failed to acquire lock
. here!tryAcquire(arg)
It's true , Save the current thread object to the queue , The source code logic is as follows :acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
Current
Try to join the queue
It is divided into two parts .First step
addWaiter(Node.EXCLUSIVE)
;
The second stepacquireQueued(addWaiter(Node.EXCLUSIVE), arg)
.among
addWaiter(Node.EXCLUSIVE)
The operation logic of is as follows :private Node addWaiter(Node mode) { // Create a Node node , Save the reference and state information of the current thread // mode Divided into two :Node.EXCLUSIVE Mutually exclusive (ReenTrantLock characteristic );Node.SHARED share (Semaphore characteristic ) Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure // Get the end of the queue Node pred = tail; if (pred != null) { // If CAS There are other Node object , Will be new node Class prev The node points to this Node node.prev = pred; // At the same time take CAS The algorithm sets the original queue , take tail Property value is set to new node object if (compareAndSetTail(pred, node)) { // Last in the original queue node Object's next Property points to the new last node object pred.next = node; return node; } } // When in the queue ,tail When there is no point on the property ( Empty queue ); Or there is a team leader but a new one is added Node To queue failed // This method has two uses : // 1、 Build queue headers , Prevent null pointer problems // 2、 New node The node is inserted into the queue , Until the insertion is successful enq(node); return node; } private Node enq(final Node node) { // Dead cycle , Infinite spin // Ensure that any thread that does not get the lock can be successfully queued for (;;) { // Get no nodes Node t = tail; // AbstractQueuedSynchronizer Medium head and tail attribute , Point to the two-way linked list , That is to say CLH The head and the tail of the line // When there is data in the queue ,head and tail Property will not be null if (t == null) { // Must initialize // When it comes to null, Indicates that there is no data information in the queue , That is, the queue has no // You need to use CAS The algorithm creates a “ Empty Node” node , And set it to AQS Medium head Go up if (compareAndSetHead(new Node())) // hold AbstractQueuedSynchronizer Of tail attribute Also set this “ Empty Node” node tail = head; // There's only one in the queue “ Empty Node” Node time ,head and tail attribute All point to This “ Empty Node” node // Enter next cycle } else { // Get the tail of the current queue Node Node object // Here is the new... That needs to be added to the queue node object . // New node Object's prev Property points to the last one in the previous queue node node.prev = t; // CAS Algorithm , Reset the queue tail attribute , For those who need to be added node object if (compareAndSetTail(t, node)) { // The last one in the old queue node Of next Property points to the new node object , Realize the operation of joining the team t.next = node; return t; } } } }
for There is... In the loop return, Will be terminated for loop !
enq(final Node node) Method
The execution logic is as follows :addWaiter(Node mode)
in , Judge from the beginningNode pred = tail
There is!= null
situations , Its treatment is as follows :Let's say the thread T2 Get into .
When
Lock not obtained
The thread of , Successful entryqueue ( Double linked list )
after , Here, we will proceed to the next processing method :acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
The processing logic of this method is as follows :
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; // Spin operation , It must be ensured that threads can block for (;;) { // Get current node Object's prev Property value , In other words, the queue should node The last one of node object final Node p = node.predecessor(); // If it's time to node object p And AQS Medium head matching ( Queue header ), Then try to get the lock again // because : Structure of double linked list , The head of the team is a “ Empty Node object ” // Try not to block your threads ( Switching between kernel state and user state , Time consuming and inefficient ) if (p == head && tryAcquire(arg)) { // If you get the lock , Then the previous node goes out of the queue // take AQS Of head The header node is set to the current node // The previous in the queue Node Object thread Property is set to null( Because of this Node The thread in gets the lock ) // To make a long story short : // The thread object gets the lock , There is no need to remain in the queue ; // Put it Node Object thread Attribute is set to null, Become a ” empty Node node “, To be the new team leader setHead(node); // Put the old leader in the queue next clear ( Because the new node Set up to be the team leader , The previous team leader needed gc) p.next = null; // help GC failed = false; return interrupted; } // If you don't get the lock , You need to do the following ---- Blocking threads // shouldParkAfterFailedAcquire Set up Node Nodes in the waitStatus Property value // parkAndCheckInterrupt call LockSupport.park( Threads ), Block the current thread // Because of the Node object ,waitStatus The default value is 0; // 1、 First, the first cycle , If waitStatus The value of is default 0; // modify head state , take waitStatus It is amended as follows sinal(-1), So that it can be awakened , But what it returns is false // 2、 The next cycle , Judged waitStatus Status as sinal(-1), return true, Indicates that execution can begin parkAndCheckInterrupt() Blocking threads // At the same time, determine whether the thread is awakened by an interrupt signal ! // Be careful : // If at first waitStatus It's worth it > 0 ( Cancelled status ), Then the drive node is abolished if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } } // According to the last Node node , Determine the next... In the queue Node Whether the node can be awakened // Parameter one : At present node In the object prev Property value , The last one Node Object node // Parameter two : At present node Object node private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // Get previous node Nodes in the waitStatus value int ws = pred.waitStatus; // If meet “ Can be awakened ” The state of if (ws == Node.SIGNAL) // return true return true; if (ws > 0) { // If the state of the precursor node is cancelled , Will be removed from the queue do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { // Current drive node waitStatus by 0 or PROPAGATE In the state of // Set it to SIGNAL(-1) state , Then the current node can be safely park // Outside is a spin operation compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; } private final boolean parkAndCheckInterrupt() { // Use LockSupport Let the current Node Thread in thread Blocked LockSupport.park(this); return Thread.interrupted();// Returns whether the thread has been interrupted }
【 Be careful :】shouldParkAfterFailedAcquire(Node pred, Node node) Means :
The head of the line ( That is to say “ Empty Node object ”) Medium
waitStatus
What is kept isReally effective next Node node
OfState information
.if (p == head && tryAcquire(arg))
When the conditions are met , The execution logic is as follows :【 Expand :】java.util.concurrent.locks.AbstractQueuedSynchronizer.Node#waitStatus The meaning of several values :
SIGNAL:-1
Can be awakenedThe thread of the relay node is in a waiting state , If the current node releases the synchronization state or is cancelled ,
Subsequent nodes will be notified , Enables subsequent nodes to run threads .CANCELLED:1
Represents an exception , Caused by interruption , Need to endThe thread waiting in the synchronization queue timed out or was interrupted , Need to cancel waiting from synchronization queue
CONDITION:-2
Conditions waitThe node is in the waiting queue , The thread of the node is waiting in Condition On ,
When other threads Condition Called signal() After the method , The node moves from the waiting queue to the synchronization queue , Add to get synchronization statePROPAGATE:-3
spreadIndicates that the next shared synchronization state acquisition will be unconditionally propagated
0:
Initialization status value
Release the lock and wake up the next thread
stay Java In the code , When a thread has finished executing , Need developers in finally
Lock in code block Release operation
. Use the following code to achieve :
lock.unlock();
among , The execution logic of this method is as follows :
public void unlock() {
sync.release(1);
}
Continue to drill down , It is known that the specific implementation is :
public final boolean release(int arg)
// take AQS Medium state Modify the attribute value
// If AQS Medium state The attribute value becomes 0, Then we need to put exclusiveOwnerThread Empty the attribute value
// If meet state Become 0, And exclusiveOwnerThread Also get empty ,( That is, the successful release of the lock )
if (tryRelease(arg)) {
// that , From the two-way linked list ( queue ) In order to get head Property points to Node node
Node h = head;
// According to the head of the team Node nodes waitStatus The status value determines the next valid Node Whether the node can be awakened
// stay Node When creating nodes ,waitStatus The default data is 0
// In locking operation shouldParkAfterFailedAcquire(Node pred, Node node) operation , Will change it to sinal(-1)
// When the wakeable state is satisfied , Of course, there are other status messages
if (h != null && h.waitStatus != 0)
// Conduct Wake up operation
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
// obtain AQS Medium state Status value information , And subtract it
// Here's a little bit of attention :
// When there is a lock operation , When the lock re enters , here AQS Medium state The value information is incomplete 1!
int c = getState() - releases;
// Verify the current thread and AQS Stored in the exclusiveOwnerThread Is the data consistent
if (Thread.currentThread() != getExclusiveOwnerThread())
// Throw an exception if it is inconsistent
throw new IllegalMonitorStateException();
boolean free = false;
// When AQS Medium state The property value is 0 when , To ensure that this thread “ Completely ” Lock released
// Consider the reentry of the lock
if (c == 0) {
free = true;
// take AQS in exclusiveOwnerThread Set the value of the property to null
setExclusiveOwnerThread(null);
}
// take AQS Medium state The value is set to the new value
setState(c);
return free;
}
// Set up AQS Medium exclusiveOwnerThread attribute
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
// Wake up operation
private void unparkSuccessor(Node node) {
// Get the empty of team leader Node node , According to its waitStatus Judge the next Node Whether wakeup operation is supported
int ws = node.waitStatus;
// If Its data information <0 , Then use CAS The algorithm modifies its waitStatus value , Change it to 0
// Why is it changed back to 0 Well ? Refer to the following questions to explain !
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// Get the first air of the team Node The next valid... Of the node Node node ( Reference save of existing thread )
Node s = node.next;
// If the next node does not exist , Or its waitStatus State information >0 ( Greater than 0 Only CANCELLED) Indicates an exception , Need to be abandoned
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
// Two way linked list from back to front <= 0 Of Node node
// That is, traverse forward from the back tail to find the first node in the normal blocking state
// Wake up
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
// Get what can be awakened Node Saved in node thread
// namely : Wake up a thread that can be awakened ( It's a little tongue twister , Just know the meaning QAQ)
LockSupport.unpark(s.thread);
}
【 doubt :】 Why is it unparkSuccessor(Node node) In execution logic , There will be When node.waitStatus < 0 when , Conduct compareAndSetWaitStatus(node, ws, 0) operation ?
1、 In the locking operation , When a thread executes to
parkAndCheckInterrupt()
when , hereThe thread will be interrupted
.( Pause there !)
2、 If it isFair lock
, WhenRelease the lock unparkSuccessor(Node node)
After execution , CorrespondingSpecify... In the queue Node node
Ofthread
A thread will be awakened !
Because of the lockedparkAndCheckInterrupt()
Exist inDead cycle ( spinlocks )
in , At this point, after the specified thread is awakened , It's going to beLocking logic if (p == head && tryAcquire(arg))
Judge .
If I get the lock , The queue and AQS Information in . If you don't get the lock , Will also waitstatus Value is modified to -1.Fair lock , After the previous thread releases the lock , Qualified in the queue Node Medium thread You can definitely get the lock !
But if it is Not fair lock
Well ?
1、 stay
Not fair lock
in , Every Node After object creation , ItswaitStatus
Property values areThe default is 0
.
2、 hereThe thread holding the lock released the lock
, Qualified in the queueNode node
Thread in ,It is not always possible to acquire the lock immediately
! The lock may have been snatched away by a new thread outside !
therefore , Here in the source code , Make it compareAndSetWaitStatus(node, ws, 0)
operation !
Logic diagram
边栏推荐
- redis-1. Install redis with pictures and texts
- Un des backtraders du cadre de quantification lit l'analyseur
- P1434 [show2002] skiing (memory search
- 2022-06-12:在N*N的正方形棋盘中,有N*N个棋子,那么每个格子正好可以拥有一个棋子。 但是现在有些棋子聚集到一个格子上了,比如: 2 0 3 0 1 0 3 0 0 如上的二维数组代表,一
- Micro isolation (MSG)
- 全志V3S环境编译开发流程
- Sharp weapon tcpdump
- How to stop PHP FPM service in php7
- 2022 - 06 - 12: dans un échiquier carré n * N, il y a n * n pièces, donc chaque pièce peut avoir exactement une pièce. Mais maintenant quelques pièces sont rassemblées sur une grille, par exemple: 2 0
- Ticdc introduction
猜你喜欢
redis-4. Redis' message subscription, pipeline, transaction, modules, bloom filter, and cache LRU
在 localStorage 中上传和检索存储图像
SDN基本概述
RT-Thread 模拟器 simulator LVGL控件:button 按钮事件
怎么写出一份令人惊叹的设计文档?
【ViveFocus使用WaveVR插件获取手柄操作事件】
2022-06-12:在N*N的正方形棋盤中,有N*N個棋子,那麼每個格子正好可以擁有一個棋子。 但是現在有些棋子聚集到一個格子上了,比如: 2 0 3 0 1 0 3 0 0 如上的二維數組代錶,一
Uploading and retrieving stored images in localstorage
Problems encountered during commissioning of C # project
10 Honest Facts I Want To Share With All Junior Developers
随机推荐
Performance tuning can't just depend on tapping the brain
在 localStorage 中上传和检索存储图像
mysql中时间字段 比较时间大小
Raspberry school advanced development - "writing of IO port driver code" includes bus address, physical \u virtual address and bcm2835 chip manual knowledge
I always don't understand the high address and high position
TiDB Lightning
Reflection of C # Foundation
FTP_ Manipulate remote files
Make cer/pfx public and private key certificates and export CFCA application certificates
P6154 游走(记忆化搜索
Tikv key performance parameters and optimization
Introduction and use of dumping
Tidb data migration (DM) Introduction
尝试使用RenderDoc查看UE的Shader代码
RT-Thread 模拟器 simulator LVGL控件:button 按钮事件
An example of CSRF attack defense in web application scenarios
Monotone stack top31 of interview must brush algorithm top101
号称下一代监控系统 来看看它有多牛逼
Lightning breakpoint continuation
redis-4. Redis' message subscription, pipeline, transaction, modules, bloom filter, and cache LRU