te')); return $arr; } /* 遍历用户所有主题 * @param $uid 用户ID * @param int $page 页数 * @param int $pagesize 每页记录条数 * @param bool $desc 排序方式 TRUE降序 FALSE升序 * @param string $key 返回的数组用那一列的值作为 key * @param array $col 查询哪些列 */ function thread_tid_find_by_uid($uid, $page = 1, $pagesize = 1000, $desc = TRUE, $key = 'tid', $col = array()) { if (empty($uid)) return array(); $orderby = TRUE == $desc ? -1 : 1; $arr = thread_tid__find($cond = array('uid' => $uid), array('tid' => $orderby), $page, $pagesize, $key, $col); return $arr; } // 遍历栏目下tid 支持数组 $fid = array(1,2,3) function thread_tid_find_by_fid($fid, $page = 1, $pagesize = 1000, $desc = TRUE) { if (empty($fid)) return array(); $orderby = TRUE == $desc ? -1 : 1; $arr = thread_tid__find($cond = array('fid' => $fid), array('tid' => $orderby), $page, $pagesize, 'tid', array('tid', 'verify_date')); return $arr; } function thread_tid_delete($tid) { if (empty($tid)) return FALSE; $r = thread_tid__delete(array('tid' => $tid)); return $r; } function thread_tid_count() { $n = thread_tid__count(); return $n; } // 统计用户主题数 大数量下严谨使用非主键统计 function thread_uid_count($uid) { $n = thread_tid__count(array('uid' => $uid)); return $n; } // 统计栏目主题数 大数量下严谨使用非主键统计 function thread_fid_count($fid) { $n = thread_tid__count(array('fid' => $fid)); return $n; } ?>Critical Section in JavaScript or jQuery - Stack Overflow
最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Critical Section in JavaScript or jQuery - Stack Overflow

programmeradmin4浏览0评论

I have a webpage, in which a certain Ajax event is triggered asynchronously. This Ajax section could be called once or more than once. I do not have control over the number of times this event is triggered, nor the timing.

Also, there is a certain code in that Ajax section that should run as a critical section, meaning, when it is running, no other copy of that code should be running.

Here is a pseudo code:

  1. Run JavaScript or jQuery code
  2. Enter critical section that is Ajax (when a certain process is waiting for a response callback, then do not enter this section again, until this process is done)
  3. Run more JavaScript or jQuery code

My question is, how can I run step 2 the way described above? How do I create/guarantee a mutual exclusion section using JavaScript or jQuery.

I understand the theory (semaphores, locks, ...etc.), but I could not implement a solution using either JavaScript or jQuery.

EDIT

In case you are suggesting a Boolean variable to get into the critical section, this would not work, and the lines below will explain why.

the code for a critical section would be as follows (using the Boolean variable suggestions):

load_data_from_database = function () {

    // Load data from the database. Only load data if we almost reach the end of the page
    if ( jQuery(window).scrollTop() >= jQuery(document).height() - jQuery(window).height() - 300) {

        // Enter critical section
        if (window.lock == false) {

            // Lock the critical section
            window.lock = true;

            // Make Ajax call
            jQuery.ajax({
                type:        'post',
                dataType:    'json',
                url:         path/to/script.php,
                data:        {
                    action:  'action_load_posts'
                },
                success:     function (response) {
                    // First do some stuff when we get a response

                    // Then we unlock the critical section
                    window.lock = false;
                }

            });


            // End of critical section
        }
    }
};

// The jQuery ready function (start code here)
jQuery(document).ready(function() {
    var window.lock = false; // This is a global lock variable

    jQuery(window).on('scroll', load_data_from_database);
});

Now this is the code for the lock section as suggested using a Boolean variable. This would not work as suggested below:

  1. The user scrolls down, (and based on the association jQuery(window).on('scroll', load_data_from_database); more than one scroll event is triggered.

  2. Assume two scroll events are triggered right at almost the same moment

  3. Both call the load_data_from_database function

  4. The first event checks if window.lock is false (answer is true, so if statement is correct)

  5. The second event checks if window.lock is false (answer is true, so if statement is correct)

  6. The first event enters the if statement

  7. The second event enters the if statement

  8. The first statement sets window.lock to true

  9. The second statement sets window.lock to true

  10. The first statement runs the Ajax critical section

  11. The second statement runs the Ajax critical section.

  12. Both finish the code

As you notice, both events are triggered almost at the same time, and both enter the critical section. So a lock is not possible.

I have a webpage, in which a certain Ajax event is triggered asynchronously. This Ajax section could be called once or more than once. I do not have control over the number of times this event is triggered, nor the timing.

Also, there is a certain code in that Ajax section that should run as a critical section, meaning, when it is running, no other copy of that code should be running.

Here is a pseudo code:

  1. Run JavaScript or jQuery code
  2. Enter critical section that is Ajax (when a certain process is waiting for a response callback, then do not enter this section again, until this process is done)
  3. Run more JavaScript or jQuery code

My question is, how can I run step 2 the way described above? How do I create/guarantee a mutual exclusion section using JavaScript or jQuery.

I understand the theory (semaphores, locks, ...etc.), but I could not implement a solution using either JavaScript or jQuery.

EDIT

In case you are suggesting a Boolean variable to get into the critical section, this would not work, and the lines below will explain why.

the code for a critical section would be as follows (using the Boolean variable suggestions):

load_data_from_database = function () {

    // Load data from the database. Only load data if we almost reach the end of the page
    if ( jQuery(window).scrollTop() >= jQuery(document).height() - jQuery(window).height() - 300) {

        // Enter critical section
        if (window.lock == false) {

            // Lock the critical section
            window.lock = true;

            // Make Ajax call
            jQuery.ajax({
                type:        'post',
                dataType:    'json',
                url:         path/to/script.php,
                data:        {
                    action:  'action_load_posts'
                },
                success:     function (response) {
                    // First do some stuff when we get a response

                    // Then we unlock the critical section
                    window.lock = false;
                }

            });


            // End of critical section
        }
    }
};

// The jQuery ready function (start code here)
jQuery(document).ready(function() {
    var window.lock = false; // This is a global lock variable

    jQuery(window).on('scroll', load_data_from_database);
});

Now this is the code for the lock section as suggested using a Boolean variable. This would not work as suggested below:

  1. The user scrolls down, (and based on the association jQuery(window).on('scroll', load_data_from_database); more than one scroll event is triggered.

  2. Assume two scroll events are triggered right at almost the same moment

  3. Both call the load_data_from_database function

  4. The first event checks if window.lock is false (answer is true, so if statement is correct)

  5. The second event checks if window.lock is false (answer is true, so if statement is correct)

  6. The first event enters the if statement

  7. The second event enters the if statement

  8. The first statement sets window.lock to true

  9. The second statement sets window.lock to true

  10. The first statement runs the Ajax critical section

  11. The second statement runs the Ajax critical section.

  12. Both finish the code

As you notice, both events are triggered almost at the same time, and both enter the critical section. So a lock is not possible.

Share Improve this question edited Dec 17, 2016 at 22:09 Greeso asked Mar 3, 2014 at 15:41 GreesoGreeso 8,22915 gold badges56 silver badges83 bronze badges 12
  • 1 Create a boolean variable and set it to true when the critical section is called. When it's done, set it to false. Of course, at the beginning of the code, make sure it's not already set to true – Ian Commented Mar 3, 2014 at 15:44
  • 3 You don't need need semaphores or locks because javascript is single threaded. You just need a "flag" variable. – James Montagne Commented Mar 3, 2014 at 15:44
  • 1 @Greeso It sure would work. You must not be using the flag variable correctly – Ian Commented Mar 3, 2014 at 15:49
  • 1 Unless you are actually seeing the problem occurring, I question your logic @Greeso. As others have stated, JavaScript is single-threaded. This means that there isn't a way for 2 threads to be running an event handler at the same time and be reading/updating the window.lock variable "at the same time" (or almost the same time). If you need all executions of the AJAX triggered by the scroll to fire, and the 2nd, 3rd, etc. to wait for the 1st to finish, that is a mildly different question (one for which I'm writing an answer). – squid314 Commented Mar 3, 2014 at 16:22
  • 1 @Greeso the way you describe the two event handlers hitting the "if" one after the other is impossible. There is no interleaving, so that isn't happening. When you have a problem in puter programming, it is easy to bee embroiled by the answer you imagine, but try not to let that take over your faculties. What people are telling you is true: there is no interleaving. One event isn't interrupting the other. However, the callbacks from your callbacks might not be happening in the order you expect. This can be fixed with promises (I'm sure someone is working on a promise answer). – Ziggy Commented Mar 3, 2014 at 16:30
 |  Show 7 more ments

2 Answers 2

Reset to default 10

I think the most helpful information you provided above was your analysis of the locking.

  1. The user scrolls down, (and based on the association jQuery(window).on('scroll', load_data_from_database); more than one scroll event is triggered.

  2. Assume two scroll events are triggered right at almost the same moment

  3. Both call the load_data_from_database function

  4. The first event checks if window.lock is false (answer is true, so if statement is correct)

  5. The second event checks if window.lock is false (answer is true, so if statement is correct)

Right away this tells me that you have e to a mon (and quite intuitive) misunderstanding.

Javascript is asynchronous, but asynchronous code is not the same thing as concurrent code. As far as I understand, "asynchronous" means that a function's subroutines aren't necessarily explored in depth-first order as we would expect in synchronous code. Some function calls (the ones you are calling "ajax") will be put in a queue and executed later. This can lead to some confusing code, but nothing is as confusing as thinking that your async code is running concurrently. "Concurrency" (as you know) is when statements from different functions can interleave with one another.

Solutions like locks and semaphores are not the right way to think about async code. Promises are the right way. This is the stuff that makes programming on the web fun and cool.

I'm no promise guru, but here is a working fiddle that (I think) demonstrates a fix.

load_data_from_database = function () {

    // Load data from the database. Only load data if we almost reach the end of the page
    if ( jQuery(window).scrollTop() >= jQuery(document).height() - jQuery(window).height() - 300) {

        console.log(promise.state());
        if (promise.state() !== "pending") {
            promise = jQuery.ajax({
                type:        'post',
                url:         '/echo/json/',
                data: {
                    json: { name: "BOB" },
                    delay: Math.random() * 10
                },
                success:     function (response) {

                    console.log("DONE");
                }
            });
        }
    }
};

var promise = new $.Deferred().resolve();

// The jQuery ready function (start code here)
jQuery(document).ready(function() {

    jQuery(window).on('scroll', load_data_from_database);
});

I'm using a global promise to ensure that the ajax part of your event handler is only called once. If you scroll up and down in the fiddle, you will see that while the ajax request is processing, new requests won't be made. Once the ajax request is finished, new requests can be made again. With any luck, this is the behaviour you were looking for.

However, there is a pretty important caveats to my answer: jQuery's implementation of promises is notoriously broken. This isn't just something that people say to sound smart, it is actually pretty important. I would suggest using a different promise library and mixing it with jQuery. This is especially important if you are just starting to learn about promises.

EDIT: On a personal note, I was recently in the same boat as you. As little as 3 months ago, I thought that some event handlers I was using were interleaving. I was stupefied and unbelieving when people started to tell me that javascript is single-threaded. What helped me is understanding what happens when an event is fired.

In syncronous coding, we are used to the idea of a "stack" of "frames" each representing the context of a function. In javascript, and other asynchronous programming environments, the stack is augmented by a queue. When you trigger an event in your code, or use an asynchronous request like that $.ajax call, you push an event to this queue. The event will be handled the next time that the stack is clear. So for example, if you have this code:

function () {
    this.on("bob", function () { console.log("hello"); })

    this.do_some_work();
    this.trigger("bob");
    this.do_more_work();
}

The two functions do_some_work and do_more_work will fire one after the other, immediately. Then the function will end and the event you enqueued will start a new function call, (on the stack) and "hello" will appear in the console. Things get more plicated if you trigger an event in your handler, or if you trigger and event in a subroutine.

This is all well and good, but where things start to get really crappy is when you want to handle an exception. The moment you enter asynchronous land, you leave behind the beautiful oath of "a function shall return or throw". If you are in an event handler, and you throw an exception, where will it be caught? This,

function () {

    try {
        $.get("stuff", function (data) {
            // uh, now call that other API
            $.get("more-stuff", function (data) {
                // hope that worked...
            };
        });
    } catch (e) {
        console.log("pardon me?");
    }
}

won't save you now. Promises allow you to take back this ancient and powerful oath by giving you a way to chain your callbacks together and control where and when they return. So with a nice promises API (not jQuery) you chain those callbacks in a way that lets you bubble exceptions in the way you expect, and to control the order of execution. This, in my understanding, is the beauty and magic of promises.

Someone stop me if I'm totally off.

I would remend a queue which only allows one item to be running at a time. This will require some modification (though not much) to your critical function:

function critical(arg1, arg2, pletedCallback) {
    $.ajax({
        ....
        success: function(){
            // normal stuff here.
            ....

            // at the end, call the pleted callback
            pletedCallback();
        }
    });
}

var queue = [];
function queueCriticalCalls(arg1, arg2) {
    // this could be done abstractly to create a decorator pattern
    queue.push([arg1, arg2, queueCompleteCallback]);

    // if there's only one in the queue, we need to start it
    if (queue.length === 1) {
        critical.apply(null, queue[0]);
    }


    // this is only called by the critical function when one pletes
    function queueCompleteCallback() {
        // clean up the call that just pleted
        queue.splice(0, 1);

        // if there are any calls waiting, start the next one
        if (queue.length !== 0) {
            critical.apply(null, queue[0]);
        }
    }
}

UPDATE: Alternative solution using jQuery's Promise (requires jQuery 1.8+)

function critical(arg1, arg2) {
    return $.ajax({
        ....
    });
}

// initialize the queue with an already pleted promise so the
// first call will proceed immediately
var queuedUpdates = $.when(true);

function queueCritical(arg1, arg2) {
    // update the promise variable to the result of the new promise
    queuedUpdates = queuedUpdates.then(function() {
        // this returns the promise for the new AJAX call
        return critical(arg1, arg2);
    });
}

Yup, the Promise of cleaner code was realized. :)

发布评论

评论列表(0)

  1. 暂无评论