Non-blocking synchronization algorithm practice (III)-LatestResultsProvider

Source: Internet
Author: User
Thanks to trytocatch for posting this article. Before reading this article, you need to be familiar with happens-before and understand some basic concepts of non-blocking synchronization. This article focuses on the flexible use of the happens-before rule, as well as some tips for solving the problem and how to analyze the problem. Background original requirement: I was writing a regular expression replacement tool.

Thanks to trytocatch for posting this article. Before reading this article, you need to be familiar with happens-before and understand some basic concepts of non-blocking synchronization. This article focuses on the flexible use of the happens-before rule, as well as some tips for solving the problem and how to analyze the problem. Background original requirement: I was writing a regular expression replacement tool.

Thanks to trytocatch for posting this article.

Preface

Before reading this article, you need to be familiar with happens-before and understand some basic concepts of non-blocking synchronization. This article focuses on the flexible use of the happens-before rule, as well as some tips for solving the problem and how to analyze the problem.

Background

Original requirement: I was writing a regular expression replacement tool, which will dynamically display all matching results (including replacement Preview), text, regular expressions, parameters, when one of these data changes, the results should be updated. In order to provide a friendly interaction experience, an asynchronous request should be initiated when the data changes, the operation is completed by another independent thread, and the UI update result is notified after completion. Because it is dynamically displayed, submission is very frequent.

Requirement Description

This tool class is required to allow users to submit data frequently (this operation is indicated by "submit" after this article) and update results (this operation is indicated by "update" after this article, if an operation is in progress, cancel the operation and use the new parameter to execute the new operation. If the operation is not in progress during update ), if the current result is not the latest, wake up the thread and use the new data to execute new operations. The following two methods are used: submit and update to support manual update. That is, the results are updated only when the update button is clicked.

In addition, for the sake of practice, and for the purpose of compiling a comprehensive and practical tool, I also added some additional requirements:

1. multi-thread scenario is introduced. update and submit can be initiated by multiple threads at the same time. This tool class should be counted as thread-safe.

2. latency calculation is allowed. If submit is executed within the latency, only the latency is recalculated. If the computation is not convenient to cancel, in the case of frequent submit in a short time, the latency will be a good solution.

3. You can set a maximum delay time to supplement the delay enabling operation. When a long time of frequent submit, such a situation will occur, and it has not been involved in the operation phase, the new results cannot be calculated, but the last calculation result was a long time ago. If you want to display a newer but not the latest result, the maximum latency will be useful.

4. Provides an active Cancellation Method to cancel ongoing operations.

5. During update, you can wait for the Operation to complete and set the time-out period. When the calculation of the current or more (more specifically) new data is actively canceled, timed out, and completed, the wait ends.

If you are interested and have the energy required, you can first try to figure out how to implement it.

Problem Analysis

This tool should maintain a Status field so that the correct action can be made according to the status when an operation is initiated, for example, if the operation is not in the stopped status (or actively canceled, the reason is as follows). The operation thread does not need to be woken up when update is executed. A simple analysis shows that there should be at least several states:

1. Stop status: There is no computing task currently, and the thread enters the blocking status. After the operation is canceled and completed, it enters the status.

2. latency: If latency is enabled, it is in this status before the operation.

3. Operation Status: The operation is being executed.

4. Active cancellation status: this status is displayed when an active cancellation is initiated.

5. New Task status: when a new computing task exists, it enters this status and then enters the computing status again.

Latency

Let's take a look at the latency. If the latency is 500 milliseconds, we will perform sleep (500) each time. What should we do if we submit again during this period? Wake it up and then sleep (500) again? Obviously not, the cost is too high.

I have a tips: Divide 500 into multiple appropriate equal parts. Use a counter, sleep each time, and Add 1 to the counter. If a submit is initiated, set the counter to 0, although it seems that the thread's status switches more frequently, it should be more stable when it is reset frequently. Although the time will fluctuate up and down in an equal order, it does not need to be accurate here.

Now, how can we know that the current status is delayed and the counter is set to 0? You can retrieve the status value for determination and set it to 0. This method obviously does not work because the status may change when it is set to 0, so you cannot know whether the operation has taken effect.

The method I came up with is to introduce another delay reset status. If it is in this status, the next time the counter is added with 1, the counter is reset, And the status change can know whether it is successful or not.

Status Change

Some status changes are conditional. For example, if you are in the canceled status, you cannot change it to the computing status. The computing status can only be changed from the new task status to the delayed status (after the operation is completed, the operation is executed) or delay resetting status transfer in. This scenario is exactly the same as CAS. Therefore, a AtomicInteger is used to represent the State.

After analyzing the transition between different States, you can obtain the following status change diagram:

Blue a (bcd) | (e) F-line is the process of initiating an update in the stopped state. After the operation is completed, it returns to the stopped state. When the delay is enabled, it is bcd; otherwise, it is e.

The red line j indicates that the maximum latency is exceeded. The delay is exited and enters the computing State (or d ).

The Green Line ghi (including a) indicates how the status changes if a submit or update is initiated. If you are in a delayed reset or new task, you do not need to perform any operations. If you are in a delayed state, you can change to a delayed reset. If you are in an operational state, you may have used the old parameters, it should be converted to a new task. if the task is in the active cancel or stop state and the update method is called, it is converted to a new task and may be in the blocking state. The thread should be awakened.

The black line l indicates that an active cancellation can be initiated in any State to enter this state. Then, after notifying the waiting thread, it will be transferred to the stopped status, corresponding to the purple k. If an active cancellation is initiated in the stopped status, it will only be switched to the active cancellation status and will not be notified to the waiting thread. Therefore, when the thread is blocked, it may be in the stopped state or canceled state.

Sequence Problems

The above analysis shows that when submit is used, the latency should be converted to the delay reset or operation to the new task. Are these two attempts in a particular order?

Yes, because the normal execution process a (bcd) | (e) f, after the operation status is delayed, If you first try to convert the operation to a new task, it may be in a delayed state, so failed. When trying to reset the delay, the status will be changed from the previous delay to the operation during this period. Therefore, both attempts fail. We should reset the delay, but nothing is done. This is wrong. Change the order of the two attempts. As long as the status is delay or operation, the two state conversion attempts will certainly succeed once.

The subsequent Code contains multiple similar sequence details.

Solution

The complete code is provided below, except for the part waiting for the computation to be completed, which is implemented at the wait-free level in other places.

CalculateResult is the method for executing operations. The above submit corresponds to the updateParametersVersion method in the Code. The above update corresponds to the remaining update methods.

In the updateAndWait method, the BoundlessCyclicBarrier mentioned in the previous article is used. The maintained version number is the parameter version number ParametersVersion.

/** * @author trytocatch@163.com * @date 2013-2-2 */public abstract class LatestResultsProvider {    /** update return value */    public static final int UPDATE_FAILED = -1;    public static final int UPDATE_NO_NEED_TO_UPDATE = 0;    public static final int UPDATE_SUCCESS = 1;    public static final int UPDATE_COMMITTED = 2;    /** update return value */    /** work states*/    private static final int WS_OFF = 0;    private static final int WS_NEW_TASK = 1;    private static final int WS_WORKING = 2;    private static final int WS_DELAYING = 3;    private static final int WS_DELAY_RESET = 4;    private static final int WS_CANCELED = 5;    /** work states*/    private final AtomicInteger workState;    private int sleepPeriod = 30;    private final AtomicInteger parametersVersion;    private volatile int updateDelay;// updateDelay>=0    private volatile int delayUpperLimit;    private final BoundlessCyclicBarrier barrier;    private Thread workThread;    /**     *     * @param updateDelay unit: millisecond     * @param delayUpperLimit limit the sum of the delay, disabled     * while delayUpperLimit 0 ? WS_DELAY_RESET : WS_WORKING)) {                            if (workState.compareAndSet(WS_CANCELED, WS_OFF)) {                                barrier.cancel();                            }                            LockSupport.park();                            interrupted();                        }                        if (workState.get() == WS_DELAY_RESET) {                            int delaySum = 0;                            for (;;) {                                if (workState.compareAndSet(WS_DELAY_RESET,                                        WS_DELAYING)) {                                    sleepCount = (updateDelay + sleepPeriod - 1)                                            / sleepPeriod;                                }                                sleep(sleepPeriod);                                if (--sleepCount <= 0                                        && workState.compareAndSet(WS_DELAYING,                                                WS_WORKING))                                    break;                                if (delayUpperLimit >= 0) {                                    delaySum += sleepPeriod;                                    if (delaySum >= delayUpperLimit) {                                        if (!workState.compareAndSet(                                                WS_DELAYING, WS_WORKING))                                            workState.compareAndSet(                                                    WS_DELAY_RESET, WS_WORKING);                                        break;                                    }                                }                                if (workState.get() != WS_DELAYING                                        && workState.get() != WS_DELAY_RESET)                                    break;                            }                        }                        if (isWorking()) {                            int workingVersion = parametersVersion.get();                            try {                                calculateResult();                                if (workState.compareAndSet(WS_WORKING, WS_OFF))                                    barrier.nextCycle(workingVersion);                            } catch (Throwable t) {                                t.printStackTrace();                                workState.set(WS_CANCELED);                            }                        }                    } catch (InterruptedException e) {                        workState.compareAndSet(WS_DELAYING, WS_CANCELED);                        workState.compareAndSet(WS_DELAY_RESET, WS_CANCELED);                    }                }// for(;;)            }// run()        };        workThread.setDaemon(true);        workThread.start();    }    public int getUpdateDelay() {        return updateDelay;    }    /**     * @param updateDelay     *            delay time. unit: millisecond     */    public void setUpdateDelay(int updateDelay) {        this.updateDelay = updateDelay < 0 ? 0 : updateDelay;    }    public int getDelayUpperLimit() {        return delayUpperLimit;    }    /**     * @param delayUpperLimit limit the sum of the delay, disabled     * while delayUpperLimit= 0 ? UPDATE_SUCCESS                : UPDATE_FAILED;    }    /**     * @return FAILED, NO_NEED_TO_UPDATE, SUCCESS     * @throws InterruptedException     */    public final int updateAndWait() throws InterruptedException {        return updateAndWait(0);    }    public final boolean isResultUptodate() {        return parametersVersion.get() == barrier.getVersion();    }    /**     * be used in calculateResult()     * @return true: the work state is working, worth to calculate the     * result absolutely, otherwise you can cancel the current calculation     */    protected final boolean isWorking() {        return workState.get()==WS_WORKING;    }    /**     * you must call this after update the parameters, and before calling the     * update     */    protected final void updateParametersVersion() {        int pVersion = parametersVersion.get();        //CAS failed means that another thread do the same work already        if (parametersVersion.compareAndSet(pVersion, pVersion + 1))            if (!workState.compareAndSet(WS_DELAYING, WS_DELAY_RESET))                workState.compareAndSet(WS_WORKING, WS_NEW_TASK);    }    /**     * implement this to deal with you task     */    protected abstract void calculateResult();}

In the code, I directly open a new thread in the constructor. In general, this is not recommended. However, unless the update method is executed before the constructor is complete, otherwise, it will not cause any problems.

Finally, the introduction of this regular expression replacement tool and: http://www.cnblogs.com/trytocatch/p/RegexReplacer.html

Summary

Status changes are suitable for non-Blocking Algorithms and can reach the wait-free level. The length is limited. For details that are not mentioned, please use the code to understand them. If you have any questions, please reply to the discussion.

Series Summary

This is the end of the practical series.

Compared with lock synchronization, non-blocking synchronization is another way of thinking.

Sometimes, one step cannot be completed. It can be divided into two steps to solve the problem. concurrent1_queue does this.

If you need to maintain a certain consistency relationship between multiple data, you can encapsulate them into a class, and update the reference method of this class object when updating.

As we all know, the lock synchronization algorithm is difficult to test, and the non-blocking synchronization algorithm is more difficult to test. I personally think that its correctness mainly depends on careful scrutiny and demonstration.

Non-blocking synchronization algorithms are more complex than lock synchronization algorithms. If you do not have high performance requirements and are not familiar with non-blocking algorithms, we recommend that you do not use non-blocking algorithms, the lock synchronization algorithm is much simpler and easier to maintain. As mentioned above, two seemingly unordered statements may cause bugs if they change the order.

Original article address: non-blocking synchronization algorithm practice (III)-LatestResultsProvider, thanks to the original author for sharing.

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.