Architecture Design: Producer/consumer model [3]: Annular Buffer

Source: Internet
Author: User
Tags mail example

Today we are going to introduce the "producer/consumer model", which can be used in many development fields. This mode is very important. I plan to introduce it in several posts. Today, I am going to talk about literacy. If you know more about this mode, skip this post and read the next post (about how to determine the data unit ).
You may have heard of this in the 23 modes of the four-person gang (gof! In fact, the 23 classic gof models are mainly based on OO (from the title of design patterns: Elements of reusable object-oriented software ). The pattern can be oo or non-oo pattern.

Introduction
Let's get down to the truth! In the actual software development process, we often encounter the following scenarios: a module is responsible for generating data, and the data is processed by another module (ModuleIn a broad sense, it can be classes, functions, threads, processes, etc ). The module that generates data is visually calledProducerThe data processing module is calledConsumer.
Abstract producers and consumers alone is not enough to be a producer/consumer model. This mode also requires a buffer between the producer and consumer as a mediation. The producer puts the data into the buffer, while the consumer extracts the data from the buffer. The approximate structure is shown in.

In order not to be too abstract, let's give an example of sending a mail (although it is no longer a good time to send a mail, this example is more appropriate ). Suppose you want to send a letter, the general process is as follows:
1. You write the letter-equivalent to the producer's data
2. You put the mail in the mailbox-the producer puts the data in the buffer zone
3. The Postman extracts the mail from the mail box, which is equivalent to taking the data out of the buffer zone by the consumer.
4. The postman takes the mail to the post office for corresponding processing-equivalent to the Consumer processing data

Advantages
Some colleagues may ask: what is the use of this buffer zone? Why not let the producer directly call a function of the consumer and pass the data directly? What can we do with such a buffer?
In fact, this is very exquisite, and it has the following benefits.
◇ Decoupling
Assume that the producer and consumer are two classes. If the producer directly calls a method of the consumer, the producer will depend on the consumer (that is, coupling ). In the future, if the consumer's code changes, it may affect the producer. If both of them depend on a buffer zone, the coupling between the two is reduced accordingly.
In the preceding example, if you do not use a mailbox (that is, a buffer zone), you must directly send the mail to the postman. Some people may say that it is quite easy to directly give the postman? In fact, it is not easy. You must know who is a postman to give the mail to him (just wear the uniform on your body. If someone is fake, it will be miserable ). This produces the dependency between you and the postman (equivalent to that of the producer and consumer)StrongCoupling ). In case the postman changes people one day, You Need To Know it again (equivalent to modifying the producer code due to consumer changes ). The mailbox is relatively fixed, and the cost of relying on it is relatively low (equivalent toWeakCoupling ).
◇ Concurrency)
Another drawback is that a producer directly calls a method of a consumer. Because function calls are synchronous (or blocked), the producer has to wait until the consumer's method is returned. In case the consumer processes data slowly, the producer will waste a good time.
After the producer/consumer mode is used, the producer and consumer can be two independent concurrent subjects (Common concurrency types include process and thread, the following post will talk about applications under two concurrent types ). The producer can output the next data as soon as the data is lost to the buffer zone. Basically, there is no need to rely on the processing speed of the consumer.
In fact, this mode was mainly used to handle concurrency issues.
From the mail example. If there is no mailbox, you have to stand at the intersection with a letter and wait for the postman to come over (equivalent to the producer blocking); or the postman has to ask door-to-door who wants to send a mail (equivalent to consumer polling ). No matter which method is used, it's pretty good.
◇ Support for idle and busy periods
The buffer has another benefit. If the speed of data manufacturing is fast and slow, the benefits of the buffer zone will be reflected. When data is created quickly, the consumer cannot process the data. unprocessed data can be temporarily stored in the buffer zone. When the producer's manufacturing speed slows down, the consumer will slowly process it.
In order to fully reuse the information, let's take the mail example as an example. Assume that the postman can only take away 1000 messages at a time. If a greeting card is sent on Valentine's Day (or Christmas), more than 1000 emails need to be sent. At this time, the mailbox buffer zone will be used. The postman saves the delayed emails in the inbox and takes them again when they arrive.
With so much saliva, I hope that students who do not know much about the producer/consumer model can understand what it is. In the next post, let's talk about how to determine the data unit.

 

Architecture Design: Producer/consumer model [1]: How to determine a data unit? Since the previous post has already completed literacy, we should start to talk about some specific programming technical issues. However, before entering the specific technical details, we must first understand the question: how to determine the data unit? Only by clearly analyzing data units can we develop the technical design later.

What is a data unit?
What is data unit pinching? Simply put, each producer put in a buffer zone is a data unit. Each consumer fetches data from the buffer zone. For the example sent in the previous post, we can regard each letter as a data unit.
However, this introduction is too simple to help everyone analyze this stuff. So let's take a look at the features that data units need. After understanding these features, it is easy to analyze what is suitable for data units from complicated business logic.

Features of data units
To analyze data units, consider the following features:
◇ Associate with Business Objects
First, data units must be associated with certain business objects. When considering this issue, you must have a deep understanding of the current producer/consumer modelBusiness LogicTo make a proper judgment.
Because the business logic of "Sending mail" is relatively simple, everyone can easily determine what a data unit is. But in real life, it is often not so optimistic. Most of the business logic is complex. The business objects contained in the logic are of various levels and different types. In this case, it is difficult to make a decision.
This step is very important. If the wrong business object is selected, the complexity of subsequent programming and coding implementation will be greatly increased, increasing the development and maintenance costs.
◇ Integrity
Integrity means to ensure the integrity of the data unit during transmission. OrWholeThe data unit is either passed to the consumer, or completely not to the consumer. Not AllowedPartTransfer situation.
For a mail, you cannot put half a mail into a mail box. Similarly, the postman can neither take the mail from the mail box, nor just take out a part of the mail.
◇ Independence
The so-called independence means that each data unit is not mutually dependent. The transmission failure of a data unit should not affect the unit that has completed the transmission, nor affect the unit that has not yet been transmitted.
Why does the transmission fail? If the producer's production speed exceeds the consumer's processing speed for a period of time, it will cause the buffer to grow and reach the upper limit, and the subsequent data units will be discarded. If data units are independent of each other and the producer's speed drops, subsequent data units will not be affected. Otherwise, if some coupling exists between data units, the discarded data units will affect the processing of other units in the future, which will complicate the program logic.
For a mail, the loss of a mail will not affect the delivery of subsequent mail; of course, it will not affect the delivery of delivered mail.
◇ Granularity
As mentioned above, data units must be associated with certain business objects. So do data units and business objects need to be interconnected one by one? In many cases, it is indeed a one-to-one correspondence.
However, sometimes N business objects may be packaged into a data unit for performance and other reasons. Then, how to set the N value is a matter of granularity. The granularity is exquisite. A large granularity may cause some waste; a small granularity may cause performance problems. The balance of granularity should be based on many factors, as well as some experience values.
Or an example of sending a mail. If the granularity is too small (for example, set to 1), the postman will retrieve only one letter at a time. If there are too many letters, you have to go back and forth for a long time.
If the granularity is too large (for example, set it to 100), the sender must wait until 100 messages are collected to put them into the mailbox. If you rarely write a letter at ordinary times, you have to wait for a long time.
Some may ask: Can the granularity of the producer and consumer be set to different sizes (for example, set the sender to 1 and the postman to 100 ). Of course, this can be done theoretically, but in some cases it will increase the complexity of Program Logic and code implementation. We may discuss the specific technical details later.

Well, this is the topic of data units. I hope that through this post, everyone can understand what the data unit is. Next post, let's talk about the technical implementation of "queue-based buffer zone. Architecture Design: Producer/consumer model [2]: The queue buffer is paved with the previous two posts. Today we finally start to talk about some specific programming technologies. Different buffer types and concurrency scenarios have a great impact on specific technical implementation. To make it easy for everyone to understand, let's first introduce the most traditional and common methods. That is, a single producer corresponds to a single consumer.Queue(FIFO) as a buffer.
Regarding the concurrency scenario, in the previous post, "is the process still a thread? It is a problem !" The advantages and disadvantages of processes and threads have been discussed. Therefore, the process and thread modes will be mentioned in the introduction of various buffer types at the same time.

Thread mode
Let's take a look at the example of using a queue in a concurrent thread and its advantages and disadvantages.
Memory Allocation performance
In the thread mode, each producer and consumer is a thread. The producer writes data to the queue header (push), and the consumer reads data from the end of the queue (pop ). When the queue is empty, the consumer will take a moment (take a break); when the queue is full (up to the maximum length), the producer will take a moment. The entire process is not complex.
So what are the problems in the above process? A major problem is the performance overhead of memory allocation. For common queue implementation: each push may involveHeap memoryIn each pop, it may involveHeap memory. If producers and consumers are diligent and frequently push and pop, the memory allocation overhead will be considerable. For memory allocation overhead, students who use Java can refer to the post "Java performance optimization [1]" in the previous days. For students who use C/C ++, we must be clear about the underlying OS mechanism and should be aware of the allocation.Heap memory(New or malloc) There will be lock overhead and overhead of user/core switching.
What should I do? Let's take a look at the following breakdown for the "producer/consumer mode [3]: Ring buffer ".
Synchronization and mutex Performance
In addition, because the two threads share a queue, it will naturally involve such painstaking tasks as synchronization, mutex, deadlock, and so on. Fortunately, the course "Operating System" has a detailed introduction to this. Should I still have some impressions? You don't have to worry about this course. There are a lot of online introductions (such as "here"). Let's just take a look. Today we are not talking about the details.
The performance overhead of synchronization and mutex will be discussed in detail. In many cases, the use of such things as semaphores and mutex also has a great deal of overhead (in some cases, it may also lead to user/core switching ). If producers and consumers are both diligent, these expenses should not be underestimated.
What should I do? Please refer to the following breakdown for "producer/consumer mode [4]: Dual-buffer ".
Suitable for queue scenarios
I criticized the shortcomings of the queue just now. Is the queue method useless? None. Because queue is a common data structure, most programming languages have built-in queue support (for details, see "here "), some languages even provide thread-safe queues (for example, ArrayBlockingQueue introduced by JDK 1.5 ). Therefore, developers can seize the opportunity to avoid re-inventing the wheel.
Therefore, if your data traffic is not large, the benefits of using the queue buffer are obvious: clear logic, simple code, and easy maintenance. It is in line with the KISS Principle.

Process Method
After the thread method is completed, we will introduce the process-based concurrency.
The cross-process producer/consumer mode relies heavily on the specific inter-process communication (IPC) mode. However, IPC has a wide variety of names, which cannot be listed one by one (after all, the saliva is limited ). Therefore, we have chosen several cross-platform and programming languages that support a large number of IPC methods.
Anonymous Pipeline
It is felt that the MPs queue is the most queue-like IPC type. The producer process in the pipelineWrite endPut data. The consumer process extracts data from the read end of the pipeline. The effect of the entire process is very similar to that of using a queue in a thread. The difference is that you don't have to worry about thread security, memory allocation, and other things when using pipelines (the operating system is done for you in the dark ).
The MPs queue is divided into named MPs queues and anonymous MPs queues. Today, we primarily talk about anonymous MPs queues. Because named pipelines vary greatly in different operating systems (for example, Win32 and POSIX, the API interfaces and functions of named pipelines are significantly different; some platforms do not support named pipelines, for example, Windows CE ). Apart from operating system problems, for some programming languages (such as Java), named pipelines cannot be used. So I generally do not recommend this.
In fact, the APIs of anonymous pipelines on different platforms are also different (for example, the usage of Win32 CreatePipe and POSIX pipe is very different ). However, we can only use standard input and standard output (hereinafter referred to as stdio) for inbound and outbound data. Then, use the shell pipeline to associate the producer process with the consumer process (if you have never heard of this method, you can see "here "). In fact, many operating systems (especially POSIX-style) Come with commands that fully utilize this feature to implement data transmission (such as more and grep ).
There are several advantages to doing so:
1. Basically, all operating systems support using MPs queues in shell mode. Therefore, it is easy to implement cross-platform.
2. Most programming languages can operate stdio, so cross-programming languages are easy to implement.
3. As mentioned earlier, the MPs queue method saves the trouble of thread security. This helps reduce development and debugging costs.
Of course, this method also has its own shortcomings:
1. producer and consumer processes must be on the same host and cannot communicate with each other. This disadvantage is obvious.
2. This method works well in one-to-one scenarios. But if you want to extend to one-to-many or multiple-to-one, it will be a bit tricky. Therefore, the scalability of this method requires a discount. If similar extensions are to be considered in the future, this disadvantage is obvious.
3. Because the MPs queue is created by shell, the processes on both sides are invisible (the program only sees stdio ). In some cases, the program is not easy to manipulate the pipeline (such as adjusting the buffer size of the pipeline ). This disadvantage is not obvious.
4. Finally, data can only be transmitted in one way. Fortunately, in most cases, the consumer process does not need to transmit data to the producer process. If you really need information feedback (from the consumer to the producer), it will be difficult. You may have to consider replacing the IPC method.
By the way, let's take a look at the following points:
1. Read and Write operations on stdio are blocked. For example, if there is no data in the MPs queue, the read operation of the consumer process will stop until the MPs queue refresh the data.
2. Because stdio has its own buffer zone (this buffer zone is the same as the MPs queue buffer zone), it sometimes causes some unpleasant phenomena (for example, the producer process outputs data, but the consumer process does notNowRead ). For more information, see "here ".

SOCKET (TCP Mode)
TCP-based SOCKET communication is another IPC method similar to the queue. It also ensures the arrival of data in sequence; it also has a buffer mechanism. This is also cross-platform and cross-language. It is similar to the shell pipeline method just introduced.
What are the advantages of SOCKET over shell Pipe operators? It has the following advantages:
1. the SOCKET mode can be cross-machine (easy to implement distributed ). This is the main advantage.
2. the SOCKET method facilitates future expansion into multiple-to-one or one-to-many. This is also the main advantage.
3. the SOCKET can be configured with blocking and non-blocking methods, which are flexible to use. This is a secondary advantage.
4. SOCKET supports two-way communication, which facilitates consumer feedback.
Of course, there are advantages and disadvantages. Compared with the preceding shell pipeline method, using socket programming is more complex. Fortunately, our predecessors have already done a lot of work and developed many socket communication libraries and frameworks for everyone (such as C ++ ace library and Python twisted ). With the help of these third-party libraries and frameworks, the socket method is quite easy to use. Since the usage of a specific network communication library is not the focus of this series, I will not elaborate on it here.
Although TCP is more reliable than UDP in many aspects, due to the inherent unpredictability of cross-machine communication (for example, the network cable may be pulled incorrectly by a silly X, and the network may fluctuate greatly ), in terms of program design, we still need to retain more hands. What should I do? You canProcessAnd consumersProcessBased onThread"Producer/consumer model ". This sounds like a tongue twister. To make it easy to understand, draw a picture to show everyone.

The key to doing so is to divide the code into two parts: the production line and consumption thread are codes related to the business logic (irrelevant to the communication logic ); the sending and receiving threads are communication-related codes (independent from the business logic ).
The benefits are obvious, as shown below:
1. Ability to respondTemporary. You can continue to work after the network fault is rectified.
2. How to Handle network faults (for example, try to reconnect after disconnection) only affects the sending and receiving threads, and does not affect the production line and consumption threads (business logic ).
3. The specific socket method (blocking and non-blocking) only affects the sending and receiving threads, and does not affect the production line and consumption threads (business logic part ).
4. It does not rely on the sending and receiving buffers of TCP itself. (The default TCP buffer size may not meet the actual requirements)
5. Changes in business logic (such as changes in business requirements) do not affect the sending and receiving threads.
For the last article above, I would like to say a few more words. If multiple processes in the entire business system adopt the above model, you may be able to refactor one by one: cutting the line between the business logic code and the communication logic code, encapsulate the unrelated parts of the business logic into a Communication MiddlewareMiddlewareLooks cool x :-). If everyone is interested in this stuff, they will post a chat later.
Architecture Design: Producer/consumer mode [3]: The previous post in the circular buffer area mentioned the possible performance problems and solutions of the queue buffer zone: the circular buffer zone. This topic is described here today.
In order to prevent someone from giving us a "overly-designed" big hat, we declare that only when the storage space is allocated/released is veryFrequentAnd it does produceObviousYou should consider the use of the ring buffer. Otherwise, we recommend that you use the most basic and simple queue buffer. Note the following:Storage spaceNot only memory, but also storage media such as hard disks.

Ring buffer vs queue Buffer
◇ External interfaces are similar
Before introducing the circular buffer, let's review common queues. A common queue has a write end and a read end. When the queue is empty, the reading end cannot read data. When the queue is full (maximum size), the writing end cannot write data.
For users, the ring buffer is the same as the queue buffer. It also has a write end (for push) and a read end (for pop), as well as a buffer that is "full" and "empty. Therefore, switching from the queue buffer to the ring buffer can be a smooth transition for users.
◇ Different internal structures
Although the two have similar external interfaces, the internal structure and operation mechanism are very different. The internal structure of the queue is not long-winded here. This section focuses on the internal structure of the ring buffer.
Everyone can think of the Reading end (R) and writing end (W) of the circular buffer zone as two people chasing the stadium runway (R chasing W ). When R catches up with W, the buffer zone is empty. When W catches up with R (W is a lap longer than R), the buffer zone is full.
For the sake of image, find a picture and make slight changes, as shown below:

It can be seen that all the push and pop operations in the ring buffer are in the sameFixed. When the queue buffer is pushed, storage space may be allocated to store new elements. When pop is used, the storage space of discarded elements may be released. Therefore, compared with the queue mode, the ring mode reduces the allocation and release of the storage space used by the buffer elements. This is a major advantage of the ring buffer.

Implementation of circular buffer
If you already have a ready-made ring buffer available and you are not interested in the internal implementation of the ring buffer, you can skip this section.
◇ Array vs linked list
The internal implementation of the ring buffer can be implemented based on arrays (arrays here refer to contiguous buckets) or linked lists.
An array is a one-dimensional continuous linear structure in physical storage. during initialization, the storage spaceOne-timeAllocated, which is an advantage of the array method. However, to use an array to simulate the ring, you must logically connect the header and tail of the array. When traversing the array sequentially, special processing is required for the tail element (the last element. When accessing the next element of the tail element, You need to return to the Header element (0th elements ). As shown in:

Using a linked list is the opposite of an array: The linked list saves the special processing of the first and last links. However, the linked list is cumbersome during initialization, and in some cases (such as the cross-process IPC mentioned later) it is not easy to use.
◇ Read/write operations
Two indexes must be maintained in the ring buffer, corresponding to the write side (W) and the read side (r) respectively ). When writing (push), first make sure the ring is not full, then copy the data to the elements corresponding to W, and finally point w to the next element; when reading (POP, first, make sure that the ring is not available, then return the corresponding elements of R, and then R points to the next element.
◇ Judge "empty" and "full"
The above operations are not complex, but there is a small trouble: When the Blank Ring and the full ring, R and W both point to the same location! In this way, you cannot determine whether it is "empty" or "full ". There are two ways to solve this problem.
Method 1: Keep an element unused.
When the ring is empty, R and W overlap. When W is faster than R and there is an element gap between R, it is considered that the ring is full. When the storage space occupied by elements in the ring is large, this method is very earthy (a waste of space ).
Method 2: maintain additional variables
If you do not like the above method, you can also use additional variables to solve the problem. For example, you can use an integer to record the number of elements saved in the current ring (this integer> = 0 ). When R and W overlap, you can use this variable to know whether it is "null" or "full ".
◇ Element storage
Because the circular buffer itself is to reduce the overhead of storage space allocation, the type of elements in the buffer must be selected. Store as much as possibleValueData Type, instead of StoragePointer (reference)Type of data. Because pointer-type data can cause the distribution and release of storage space (such as heap memory), the effect of the ring buffer is compromised.

Application scenarios
The implementation mechanism inside the ring buffer is introduced just now. According to the practice of the previous post, we will introduce the use of online and process methods.
If the programming language and development library you are using comes with a ready-made,MatureRing buffer, it is strongly recommended to use a ready-made library, do not re-create the wheel; do not find the ready-made, to consider their own implementation. If you are a trainer in your spare time, let alone.
◇ Used for concurrent threads
Similar to the queue buffer in a thread, thread security should also be considered in the circular buffer in the thread. Unless the library of the ring buffer you are using has helped you achieve thread security, you still have to do it yourself. The circular buffer in the thread mode is used a lot, and there are also a lot of related online information. The following is a rough introduction.
For C ++ programmers, it is strongly recommended to use the circular_buffer template provided by boost. This template was first introduced in boost 1.35. In view of boost's position in the C ++ community, everyone should be able to use this template with confidence.
For C programmers, you can look at the open-source project circbuf. However, this project is based on the GPL Protocol and is not very good. It is not very active. There is only one developer. Everyone should be cautious! We recommend that you use it for reference only.
For C # programmers, refer to an example in CodeProject.
◇ Used for Concurrent Processes
The circular buffer between processes seems to have few ready-made libraries available. Everyone had to do it by themselves.
It is applicable to the IPC type of inter-process ring buffering. Common examples include shared memory and files. In these two methods, ring buffering is usually implemented using arrays. The program allocates a fixed-length storage space in advance, and then the specific read/write operations, judgment of "null" and "full", element storage and other details can be referred to the above.
The shared memory mode has good performance and is suitable for scenarios with large data traffic. However, some languages (such as Java) do not support shared memory. Therefore, this method has some limitations in the multi-language collaborative development system.
The file method supports very well in programming languages, and almost all programming languages support file operations. However, it may be limited by the performance of Disk I/O. Therefore, the file method is not suitable for fast data transmission. However, for some situations where "data units" are very large, the file method is worth considering.
For the ring buffer between processes, we also need to consider the synchronization and mutex among processes. We will not go into detail here. Architecture Design: Producer/consumer model [4]: Dual-buffer "dual-buffer" is a widely used method. The most widely used method must be in the area related to screen painting (mainly to Reduce screen flashes ). In addition, dual buffering is also frequently used in device drivers and industrial controls. But what we want to talk about today is not aimed at a specific area above, but focuses on synchronization/mutex overhead in concurrency.

Why dual-buffer?
I remember when I introduced the queue buffer a few days ago, I mentioned two performance problems of the common queue Buffer: "memory allocation overhead" and "synchronization/mutex overhead" (forgetful students, go back to the post to review it ). The "memory allocation overhead" has been solved when we introduce the circular buffer. The dual buffer we will introduce today is directed at the synchronization/mutex overhead.
In order to prevent someone from giving us a "overly-designed" big hat, another advance statement is come: only when the synchronization or mutex overhead is very obvious, you should consider the use of dual-buffer. Otherwise, everyone should be honest and practical, the most basic and simple queue buffer.

Dual-buffer Principle
I spoke a little nonsense before. Now I want to start with the subject and talk about the specific implementation.
The so-called "dual-buffer" requires two buffers (A and B ). The two buffers are always used by the producer and the other by the consumer. After the two buffers are completed, perform another switchover (previously written by the producer to be read by the consumer, and read by the previous consumer to be written by the producer ). Because the producer and consumer do notAt the same timeOperationSameBuffer (no conflict), so you do not need to read or writeEveryData Units are synchronized or mutually exclusive. By the way, this is another example.Space Change TimeOptimization ideas.
However, the two buffers alone are not enough. In order to achieve "no conflict", we have to create two mutex locks (La AND Lb), which correspond to the two buffers respectively. If the producer or consumer wants to operate a buffer, it must first have the corresponding mutex lock. To achieve the effect of "no conflict", there are actually many ways to do it. Today we just want to pick a simple chat.

Several statuses of Dual-Buffer
To help some students better understand the status of the dual-buffer zone.
◇ Status of both buffers (concurrent read/write)
In most cases, both the producer and consumer are in the concurrent read/write status. Set producer to write data to A and consumer to read data from B. In this state, the producer owns the La lock; similarly, the consumer owns the Lb lock. Because the two buffers are in the exclusive State, each read/write buffer element (data unit) isNoThen perform the lock and unlock operations. This is the main source of cost saving.
◇ Idle status of a single buffer zone
Because the speed of two concurrent entities varies, it is inevitable that one buffer has been completed, and the other has not been completed. Assume that the producer is faster than the consumer.
In this case, when the producer fully writes A, the producer must first release La (indicating that it no longer operates A) and then try to obtain Lb. B has not been read empty, and Lb is held by the consumer, so the producer enters the Suspend state.
◇ Buffer Switching
Next, we will discuss the topic above.
After some time, the consumer finally finished reading B. At this time, the consumer must first release Lb and then try to obtain La. Since La has just been released by the producer, the consumer can immediately own La and start to read data from. Due to the release of Lb by the consumer, the producer in the daze will be slowed down by Resume and have Lb, and then the producer will continue to write data to B.
After performing the preceding steps, the two buffers have been reversed and changed to: The producer writes B and the consumer reads.

Possible concurrency Problems
Originally, the producer/consumer issue of a single buffer zone is already a classic issue of textbooks. Now we have two buffers, which makes it even more time consuming brain cells. When you are not careful, you will get some concurrent bugs, and the concurrent bugs are difficult to debug and test (that is why you should not use them easily ).
◇ Deadlock
Assume that the operation steps described above are switched to the following sequence: After the producer or consumer completes the current buffer, the producer obtains the Lock of another buffer and releases the lock of the current buffer. What will happen?
Once two concurrent entitiesAt the same timeAfter processing the respective bufferAt the same timeTo obtain the lock owned by the other party, a typical deadlock will occur (for detailed explanations of the deadlock, see "here. Since then, they have been in a desperate situation.

Application scenarios
After introducing the concurrency issue, follow the conventions of this series, and finally introduce the application of dual-buffer in some scenarios.
◇ Used for concurrent threads
In the thread mode, the first thing to consider is the buffer type: queue mode or ring mode. The basis for this selection has already been explained when introducing the circular buffer, so it is no longer cool (saving a lot of saliva ).
Another thing to note is that some programming languages or libraries provide thread-safe buffers (for example, ArrayBlockingQueue introduced by JDK 1.5 ). Because this buffer will automatically synchronize/mutually exclusive for each read/write operation, the advantage of double buffering is eliminated. Therefore, everyone should avoid this type of buffer when selecting the buffer zone.
◇ Used for Concurrent Processes
To use dual buffering between processes, we must first examine the features of different IPC types. As the purpose of discussing double buffering today is to reduce the synchronization/mutex overhead, for those IPC types that have encapsulated synchronization/mutex, there is not much need to do double buffering (this article alone has ruled out a variety of IPC types ). Among the remaining IPC types, dual-buffering is preferred: Shared Memory and files. Coincidentally, the characteristics and applicability of these two things have also been introduced in the post of the ring buffer, which can save a lot of saliva.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.