当前位置:网站首页>Epoll principle and Application & ET mode and lt mode
Epoll principle and Application & ET mode and lt mode
2022-06-11 02:23:00 【Prison code department】
[Ⅰ] Epoll Principle and Application && ET Patterns and LT Pattern
- [Ⅱ] Epoll Reactor model The core principle && Code explanation
- [Ⅰ] Epoll Principle and Application && ET Patterns and LT Pattern
[Ⅱ] Epoll Reactor model The core principle && Code explanation
The second part is the article link : Epoll Reactor model The core principle && Code explanation
[Ⅰ] Epoll Principle and Application && ET Patterns and LT Pattern
epoll yes Linux Lower multiplexing IO Interface select/poll Enhanced version of , It can Significantly improve the system when the program is only a few active in a large number of concurrent connections CPU utilization , Because it reuses the collection of file descriptors to deliver results without forcing developers to prepare the collection of file descriptors to be listened to again before each event , Another reason is to get events , It does not have to traverse the entire set of descriptors being listened to , Just go through the kernel IO Events wake up asynchronously and join Ready The set of descriptors for the queue is enough .
at present epell yes linux Popular model of choice in large-scale concurrent network programs .
epoll In addition to providing select/poll That kind of IO The level of the event triggers (Level Triggered) Outside , It also provides edge trigger (Edge Triggered), This makes it possible for user-space programs to cache IO state , Reduce epoll_wait/epoll_pwait Call to , Improve application efficiency .
One 、 breakthrough 1024 File descriptor limit
1.1 View file descriptor limits
- have access to
catCommand to view the current computer ( Or virtual machines ) The maximum number of files that can be opened , Affected by hardware configuration .
cat /proc/sys/fs/file-max
- It can be used
ulimit -asee ,open file It indicates the maximum number of file descriptors that can be opened by the process under the current user by default , Default is 1024.

The image above open files The value is changed .
1.2 Modify file descriptor restrictions
① You can modify the upper limit by modifying the configuration file .
sudo vi /etc/security/limits.conf
Write the following configuration at the end of the file ,soft Soft limit ,hard Hard limit . As shown in the figure below :
* soft nofile 65536
* hard nofile 100000

② soft The value can be modified by the command , But not more than hard value :
ulimit -n [ Modified soft value ]
Log off the user after the change , Make it effective .
Two 、Epoll Basics API
epoll Is characterized by a balanced binary tree ( The height difference between the left and right subtrees shall not exceed 1), More strictly, it is a red black tree ;
The listening tree is created by the kernel , And provide the user with access to API To add or delete query nodes .
2.1 epoll_create()
Create a epoll Handle , Parameters size The number of file descriptors used to tell the kernel to listen to , It's about memory size .
#include <sys/epoll.h>
int epoll_create(int size)
Parameters :
- size —— Number of listening text descriptors ( reference value , That is, the size is not limited , It can be dynamically expanded )
Return value :
- success —— Return the newly created root node that listens to the red black tree epfd
- Failure —— return -1 Juxtaposition errno
2.2 epoll_ctl()
Control someone epoll Events on the monitored file descriptor : register 、 modify 、 Delete .
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
Parameters :
int epfd—— epoll_create() The return value of , namely epoll Listen to the root node of the red black treeint op—— The specific operation of this monitoring red black tree , Defined by macro :EPOLL_CTL_ADD: add to fd To epoll Listen to the red and black treesEPOLL_CTL_MOD: modify fd stay epoll Monitor events on the red black treeEPOLL_CTL_DEL: Will a fd from epoll Listen to the red and black trees ( Cancel monitoring )
int fd—— File descriptor to be operatedstruct epoll_event *event—— Structure pointer ( Address ), Are incoming and outgoing parametersstruct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ };struct epoll_eventIn structureeventsOn behalf of listening events , Value for :EPOLLIN : Indicates that the corresponding file descriptor can be read ( Including the end SOCKET Normally shut down ) EPOLLOUT: Indicates that the corresponding file descriptor can write EPOLLPRI: Indicates that the corresponding file descriptor has urgent data readability ( This should indicate that there is out-of-band data coming in ) EPOLLERR: Indicates that there is an error in the corresponding file descriptor EPOLLHUP: Indicates that the corresponding file descriptor is suspended ; EPOLLET: take EPOLL Set edge trigger (Edge Triggered) Pattern , This is relative to the horizontal trigger (Level Triggered) In terms of the EPOLLONESHOT: Listen for only one event , After listening to this event , If you need to keep listening to this socket Words , I have to do this again socket Add to EPOLL In the queuestruct epoll_eventUnion parameters in the structureepoll_data_t dataIt's an outgoing parameter :typedef union epoll_data { void *ptr; int fd; // The fd It's the introduction epoll_ctl() Of the corresponding listening event fd uint32_t u32; uint64_t u64; } epoll_data_t;
Return value :
- success —— return 0
- Failure —— return -1 Juxtaposition errno
2.3 epoll_wait()
Wait for an event to occur on the monitored file descriptor , Be similar to select() call .
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
Parameters :
int epfd—— epoll_create() The return value of , namely epoll Listen to the root node of the red black treestruct epoll_event *events—— Note here and the collection of events used to store the kernelBe careful Here and epoll_ctl() Functional
struct epoll_event *eventParameters ( A pointer to a structure , Incoming and outgoing parameters ) Dissimilarity , thereeventsIt's an outgoing parameter , And it's an array , Used to store a collection of satisfaction events .
int maxevents—— Tell the kernel this events How big is theFor example, the outgoing parameters are defined
struct epoll_event ret[1024], thatmaxeventsFill in the value of 1024, amount to buffer_size;But pay attention to this maxevents Value of cannot be greater than create epoll_create() At the time of the size( Generally take the same value ).
int timeout—— Set timeout-1: Blocking
0: Return immediately , Non blocking
> 0: Specify milliseconds
Return value :
- success —— Returns how many file descriptors are ready , If timing blocking or non blocking is set, the timeout returns 0
- error —— return -1
3、 ... and 、Epoll socket Basic use cases
3.1 Server
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <errno.h>
#include <ctype.h>
#define MAXLINE 8192
#define SERV_PORT 8000
#define OPEN_MAX 5000
int main(int argc, char *argv[])
{
int i, listenfd, connfd, sockfd;
int n, num = 0;
ssize_t nready, efd, res;
char buf[MAXLINE], str[INET_ADDRSTRLEN];
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // Port multiplexing
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
listen(listenfd, 20);
efd = epoll_create(OPEN_MAX); // establish epoll Model , efd Point to the root node of the red black tree
if (efd == -1)
perr_exit("epoll_create error");
struct epoll_event tep, ep[OPEN_MAX]; //tep: epoll_ctl Parameters //ep[] : epoll_wait Parameters
tep.events = EPOLLIN;
tep.data.fd = listenfd; // Appoint lfd The listening time is " read "
res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep); // take lfd And the corresponding structure set to the tree ,efd You can find the tree
if (res == -1)
perr_exit("epoll_ctl error");
for ( ; ; ) {
/*epoll by server Blocking listening Events , ep by struct epoll_event Type array , OPEN_MAX Is the array capacity , -1 The watch is permanently blocked */
nready = epoll_wait(efd, ep, OPEN_MAX, -1);
if (nready == -1)
perror("epoll_wait error");
for (i = 0; i < nready; i++) {
if (!(ep[i].events & EPOLLIN)) // If not " read " event , Continue to cycle
continue;
if (ep[i].data.fd == listenfd) {
// Judge the satisfaction of the event fd Is it right? lfd
clilen = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen); // Accept link
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
printf("cfd %d---client %d\n", connfd, ++num);
tep.events = EPOLLIN; tep.data.fd = connfd;
res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep); // Join the red and black trees
if (res == -1)
perror("epoll_ctl error");
} else {
// No lfd,
sockfd = ep[i].data.fd;
n = read(sockfd, buf, MAXLINE);
if (n == 0) {
// Read 0, The client closes the link
res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL); // Remove the file descriptor from the red black tree
if (res == -1)
perror("epoll_ctl error");
close(sockfd); // Close the link to the client
printf("client[%d] closed connection\n", sockfd);
} else if (n < 0) {
// error
perror("read n < 0 error: ");
res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL); // Remove the node
close(sockfd);
} else {
// Number of bytes actually read
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]); // Turn capitalization , Write back to the client
write(STDOUT_FILENO, buf, n);
}
}
}
}
close(listenfd);
close(efd);
return 0;
}
3.2 Client
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MAXLINE 8192
#define SERV_PORT 8000
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
while (fgets(buf, MAXLINE, stdin) != NULL) {
write(sockfd, buf, strlen(buf));
n = read(sockfd, buf, MAXLINE);
if (n == 0) {
printf("the other side has been closed.\n");
break;
}
else
write(STDOUT_FILENO, buf, n);
}
close(sockfd);
return 0;
}
Four 、ET and LT Event model
EPOLL There are two models of events :
Edge Triggered (ET) Edge trigger —— Only the arrival of data triggers , Whether there is data in the cache or not .
Level Triggered (LT) Level trigger —— As long as there is data, it will trigger .
Think about the following steps :
Suppose we have a file descriptor used to read data from the pipeline (RFD) Add to epoll The descriptor .
The other end of the pipe writes 2KB The data of
call epoll_wait, And it will return RFD, Indicates that it is ready for read operation
Read 1KB The data of
call epoll_wait……
In the process , There are two working modes :
4.1 ET Pattern
ET The pattern is Edge Triggered Working mode .
If we were in 1 Step by step RFD Add to epoll The descriptor was used EPOLLET sign , Then in the first place 5 Step call epoll_wait After that, it's possible to suspend , Because the rest of the data is still in the input buffer of the file , And the data sending end is still waiting for a feedback message for the sent data . Only when an event occurs on the monitored file handle ET Working mode will report the incident . So on the 5 When you walk , The caller may give up waiting for the remaining data that still exists in the file input buffer .epoll Working in ET In mode , Non blocking socket interface must be used , To avoid blocking read due to a file handle / Blocking writes starve the task of processing multiple file descriptors . It's best to call ET Mode epoll Interface , Avoiding possible defects will be described later .
Based on non blocking file handle
Only when read perhaps write return EAGAIN( Non blocking read , No data available ) When you need to suspend 、 wait for . But that's not to say every time read You need to read it in cycles , Until I read that it produced a EAGAIN I think that this incident has been handled , When read When the returned read data length is less than the requested data length , You can determine that there is no data in the buffer at this time , It can be considered that the event has been handled .

4.2 LT Pattern
LT The pattern is Level Triggered Working mode .
And ET The pattern is different , With LT Way to call epoll At the interface , It is equivalent to a relatively fast poll, Whether or not the following data is used .
LT(level triggered):LT It's the default way of working , And at the same time support block and no-block socket. In this way , The kernel tells you if a file descriptor is ready , Then you can be ready for this fd Conduct IO operation . If you don't do anything , The kernel will continue to inform you , therefore , This mode is less likely to make errors in programming . Conventional select/poll They are all representatives of this model .
ET(edge-triggered):ET It's a high-speed way of working , Only support no-block socket. In this mode , When the descriptor is never ready to be ready , The kernel passes through epoll Tell you . Then it assumes that you know the file descriptor is ready , And no more ready notifications will be sent for that file descriptor . Please note that , If it's not right all the time fd do IO operation ( So it becomes not ready again ), The kernel will not send any more notifications (only once).
4.3 ET Examples of patterns
In the following example code , The parent and child processes share an anonymous pipeline , Subprocesses every 5s write in 10 Bytes of data ;
Parent process passed epoll Listen to the anonymous pipeline , Each listening event is ready for the parent process to read 5 Bytes of data .
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <errno.h>
#include <unistd.h>
#define MAXLINE 10
int main(int argc, char *argv[])
{
int efd, i;
int pfd[2];
pid_t pid;
char buf[MAXLINE], ch = 'a';
pipe(pfd); // Create anonymous pipes
pid = fork(); // Parent child process sharing pipeline
if (pid == 0) {
// Son Write
close(pfd[0]);
while (1) {
//aaaa\n
for (i = 0; i < MAXLINE/2; i++)
buf[i] = ch;
buf[i-1] = '\n';
ch++;
//bbbb\n
for (; i < MAXLINE; i++)
buf[i] = ch;
buf[i-1] = '\n';
ch++;
//aaaa\nbbbb\n
write(pfd[1], buf, sizeof(buf));
sleep(5);
}
close(pfd[1]);
} else if (pid > 0) {
// Father read
struct epoll_event event;
struct epoll_event resevent[10]; //epoll_wait Ready to return event
int nready, len;
close(pfd[1]);
efd = epoll_create(10);
event.events = EPOLLIN | EPOLLET; // ET edge-triggered
//event.events = EPOLLIN; // LT Level trigger ( Default )
event.data.fd = pfd[0];
epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event);
while (1) {
nready = epoll_wait(efd, resevent, 10, -1);
printf("res %d\n", nready);
if (resevent[0].data.fd == pfd[0]) {
len = read(pfd[0], buf, MAXLINE/2);
write(STDOUT_FILENO, buf, len);
}
}
close(pfd[0]);
close(efd);
} else {
perror("fork");
exit(-1);
}
return 0;
}
The code logic of the example is as follows :
If... Is used in ET Edge trigger mode , I'm using epoll_ctl() take fd When it is connected to the monitoring tree, it is set
event.events = EPOLLIN | EPOLLET;, that epoll_wait() Only events on the rising edge will be captured , In short epoll_wait() Only return each time the child process writes data ( Whether or not there is data in the buffer );Compared with , If the default is used LT Flat edge trigger mode (
event.events = EPOLLIN; // LT Level trigger ( Default )), So as long as there is data left in the buffer , Will be considered as read event ready ,epoll_wait() Will return .The subprocess of the sample code writes to the pipeline every time 10 Bytes of data , Every time the parent process reads from the pipe 5 Bytes of data , But whether it's ET Mode or LT Pattern , The data read out by the parent process is read out in the order in which the child process writes data ;
In this example ,ET and LT The pattern difference is Every time a subprocess is written ,ET In mode epoll_wait() Only once back ,LT In mode epoll_wait() Will return twice .
【ET Detailed examples of patterns 】
- Subprocess write aaaa\nbbbb\n;
- The parent process epoll_wait() return , The parent process reads aaaa\n, At this time, there are still bbbb\n A total of five bytes of data were not read ;
- Child process dormancy 5 Wake up in seconds , Continue writing to the pipeline cccc\ndddd\n;
- The parent process epoll_wait() return , Note that the parent process does not read cccc\n, It is bbbb\n; At this time, there are still cccc\ndddd\n The data of the cross section is not read ;
** Be careful :** The pipeline has no buffer overflow problem , If the buffer is full write It will block .
4.4 The Internet socket Medium ET / LT Pattern
The following code logic and 4.3 The sample code of is basically the same , The difference is that the two communication terminals are changed from the parent-child process to server End sum client End , Communication medium ( File descriptor ) from Linux The pipeline of the local system becomes the network socket.
But the real use epoll ET The non blocking mode is used in the , Here is a simple transition .
4.4.1 Server
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#define MAXLINE 10
#define SERV_PORT 9000
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int efd;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
listen(listenfd, 20);
struct epoll_event event;
struct epoll_event resevent[10];
int res, len;
efd = epoll_create(10);
event.events = EPOLLIN | EPOLLET; /* ET edge-triggered */
//event.events = EPOLLIN; /* Default LT Level trigger */
printf("Accepting connections ...\n");
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
event.data.fd = connfd;
epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);
while (1) {
res = epoll_wait(efd, resevent, 10, -1);
printf("res %d\n", res);
if (resevent[0].data.fd == connfd) {
len = read(connfd, buf, MAXLINE/2); //readn(500)
write(STDOUT_FILENO, buf, len);
}
}
return 0;
}
4.4.2 Client
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define MAXLINE 10
#define SERV_PORT 9000
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, i;
char ch = 'a';
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
while (1) {
//aaaa\n
for (i = 0; i < MAXLINE/2; i++)
buf[i] = ch;
buf[i-1] = '\n';
ch++;
//bbbb\n
for (; i < MAXLINE; i++)
buf[i] = ch;
buf[i-1] = '\n';
ch++;
//aaaa\nbbbb\n
write(sockfd, buf, sizeof(buf));
sleep(5);
}
close(sockfd);
return 0;
}
5、 ... and 、Epoll Of ET Non blocking model
The following code is in 4.4 Code for ET The non blocking polling mechanism is updated based on the pattern .
5.1 Server
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>
#define MAXLINE 10
#define SERV_PORT 8000
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int efd, flag;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
listen(listenfd, 20);
struct epoll_event event;
struct epoll_event res_event[10];
int res, len;
efd = epoll_create(10);
event.events = EPOLLIN | EPOLLET; /* ET edge-triggered , The default is to trigger horizontally */
//event.events = EPOLLIN;
printf("Accepting connections ...\n");
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
// Set non-blocking ///
flag = fcntl(connfd, F_GETFL); /* modify connfd For non blocking read */
flag |= O_NONBLOCK;
fcntl(connfd, F_SETFL, flag);
/
event.data.fd = connfd;
epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event); // take connfd Join the monitoring red and black tree
while (1) {
printf("epoll_wait begin\n");
res = epoll_wait(efd, res_event, 10, -1); // most 10 individual , Blocking monitor
printf("epoll_wait end res %d\n", res);
if (res_event[0].data.fd == connfd) {
while ((len = read(connfd, buf, MAXLINE/2)) >0 ) // Non blocking read , polling
write(STDOUT_FILENO, buf, len);
}
}
return 0;
}
The point is ET + Perform non blocking polling after listening returns
5.2 Client
/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define MAXLINE 10
#define SERV_PORT 8000
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, i;
char ch = 'a';
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
while (1) {
//aaaa\n
for (i = 0; i < MAXLINE/2; i++)
buf[i] = ch;
buf[i-1] = '\n';
ch++;
//bbbb\n
for (; i < MAXLINE; i++)
buf[i] = ch;
buf[i-1] = '\n';
ch++;
//aaaa\nbbbb\n
write(sockfd, buf, sizeof(buf));
sleep(10);
}
close(sockfd);
return 0;
}
The second part is the article link : Epoll Reactor model The core principle && Code explanation
边栏推荐
- 安全生产月知识竞赛——新安法知多少
- SAP smartforms page feed printing automatic judgment
- [3.delphi common components] 8 dialog box
- Mentality cannot collapse
- The female programmer gives out a salary slip: the salary is high, but she feels she is 10 years old
- Large screen - full screen, exit full screen
- Polynomial multiplication
- [3.delphi common components] 7 timer
- 多级介孔有机金属骨架材料ZIF-8负载乳酸氧化酶(LOD)/四氧化三铁(Fe304)/阿霉素DOX/胰岛素/cas9蛋白/甲硝唑/大黄素甲醚
- 心态不能崩......
猜你喜欢

Shader of double sided material

SAP SMARTFORMS换页打印自动判断

Union find

接口自动化核心知识点浓缩,为面试加分

软件测试面试复盘:技术面没有难倒我,hr面却是一把挂
![[parallel and distributed systems] cache learning](/img/79/de4da45aab54bb3bec240ac36e7978.png)
[parallel and distributed systems] cache learning

Internet of things final assignment - sleep quality detection system (refined version)

The annual salary of testers in large factories ranges from 300000 to 8K a month. Roast complained that the salary was too low, but he was ridiculed by netizens?

Colab reported an error: importerror: cannot import name '_ check_ savefig_ extra_ args‘ from ‘matplotlib. backend_ bases‘

Unity serial port communication
随机推荐
NFT insider 61:animoca brands holds US $1.5 billion of encrypted assets in 340 investments
软件测试面试复盘:技术面没有难倒我,hr面却是一把挂
Optimized dispatching (thermal power, wind energy and energy storage) [matlab code implementation]
Internet of things final assignment - sleep quality detection system (refined version)
10 years of domestic milk powder counter attack: post-90s nannies and dads help new domestic products counter attack foreign brands
金属有机骨架材料Fe-MIL-53,Mg-MOF-74,Ti-KUMOF-1,Fe-MIL-100,Fe-MIL-101)负载异氟醚/甲氨蝶呤/阿霉素(DOX)/紫杉醇/布洛芬/喜树碱
【并行与分布式系统】Cache学习
Blue Bridge Cup: the sixth preliminary round - "temperature recorder"
Analysis of the difficulties in the architecture design of massive chat messages in the live broadcast room
InfoQ 极客传媒 15 周年庆征文|容器运行时技术深度剖析
Oracle collects statistics
Infinite level classification (or menu) design
QT database learning notes (II) QT operation SQLite database
InfoQ geek media's 15th anniversary solicitation | in depth analysis of container runtime Technology
优化调度(火电、风能、储能)【matlab代码实现】
环糊精金属有机骨架(β-CD-MOF)装载二巯丁二酸/大黄素/槲皮素/三氯蔗糖/二氟尼柳/奥美拉唑(OME)
腾讯测试开发岗面试上机编程题
Redis learning notes (continuously updating)
[penetration test tool bee] how to install and use the XSS penetration test tool bee?
Analysis of common ADB commands

