сериализация параллельных операций с использованием ресурсов - PullRequest
1 голос
/ 04 марта 2020

У меня есть список операций. Каждая операция использует один ресурс. Некоторые операции используют один и тот же ресурс. Например, op1 использует тот же ресурс, что и op3. символи c код:

operations=[op1,op2,op3,op4,op5]
for (let i=0;i<operations.length;i++) {
  perform(operations[i])
}

завершается ошибкой, поскольку op1 и op3 используют один и тот же ресурс. Таким образом, другой подход:

operations=[op1,op2,op3,op4,op5]
for (let i=0;i<operations.length;i++) {
  await perform(operations[i])
}

не завершается ошибкой, но ненужный op2 ждет завершения o1, даже если они не используют тот же ресурс.

другой подход:

operations=[op1,op2,op3,op4,op5]
for (let i=0;i<operations.length;i++) {
  await operations[i].usedResource.isAvailable;
  perform(operations[i])
}

ОК, работает op1, работает op2, op3 ожидает ресурс, заблокированный op1, ... но op4 и op5 также ждут без причины.

Есть идеи?

Ответы [ 3 ]

1 голос
/ 04 марта 2020

Учитывая, что эти операции не зависят от результатов друг друга, вы можете просто создать параллельные очереди для каждого ресурса и затем запустить операции в каждом из них последовательно:

const opsByResource = new Map()
for (const op in operations) {
    const r = op.usedResource
    if (!opsByResource.has(r)) opsByResource.put(r, [])
    opsByResource.get(r).push(op)
}

return Promise.all(Array.from(opsByResource, async ([res, ops]) => {
    for (const op in ops)
        await perform(op)
    console.log("Done with all operations on", res)
}))
1 голос
/ 04 марта 2020

Вы можете сделать что-то вроде этого.

Примечание - этот код может быть написан более оптимизированным способом. это просто для того, чтобы дать представление.

let operation = (operation, time) => () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve();
        }, time);
    });
};

let op1 = operation("op1", 10000);

let op2 = operation("op2", 2000);

let op3 = operation("op3", 2000);

let op4 = operation("op4", 1000);

let op5 = operation("op5", 5000);

const operations = [
    {name: "op1", operation: op1, resource: "R1", startedAt: 0},
    {name: "op2", operation: op2, resource: "R2", startedAt: 0},
    {name: "op3", operation: op3, resource: "R1", startedAt: 0},
    {name: "op4", operation: op4, resource: "R4", startedAt: 0},
    {name: "op5", operation: op5, resource: "R5", startedAt: 0}
];

let resources = {
    "R1": {
        isAvailable: true,
        queue: []
    },
    "R2": {
        isAvailable: true,
        queue: []
    },
    "R3": {
        isAvailable: true,
        queue: []
    },
    "R4": {
        isAvailable: true,
        queue: []
    },
    "R5": {
        isAvailable: true,
        queue: []
    },
};

async function operationExecutor(operation) {
    if (operation.startedAt === 0) {
        operation.startedAt = performance.now();
    }
    if (!resources[operation.resource].isAvailable) {
        console.log("Operation", operation.name, "waiting for Resource", operation.resource);
        resources[operation.resource].queue.push(operation);
    } else {
        console.log("Operation Started", operation.name);
        resources[operation.resource].isAvailable = false;
        console.log("Resource locked", operation.resource);
        await operation.operation();
        const t1 = performance.now();
        console.log("Resource released", operation.resource);
        resources[operation.resource].isAvailable = true;
        console.log("Operation Completed", operation.name, `in ${(t1 - operation.startedAt).toFixed(2)} milliseconds`);

        if (Array.isArray(resources[operation.resource].queue) && resources[operation.resource].queue.length > 0) {
            operationExecutor(resources[operation.resource].queue.splice(0, 1)[0]);
        }
    }
}

for (let i = 0; i < operations.length; i++) {
    (operationExecutor)(operations[i]);
}
0 голосов
/ 05 марта 2020

Вдохновленный вашими решениями, я сделал это следующим образом:

  • Каждый ресурс хранит очередь блокировок (обещаний).
  • В функции выполнения я получаю первую блокировку из очереди и pu sh моя блокировка (обещание) в конце.
  • Затем дождитесь первой блокировки, выполните операцию и разрешите мою блокировку

Я широко использую свою реализацию управляемого Обещание, которое является обычным обещанием с открытой функцией "resolTo" и с возможностью автоматического разрешения после указанного времени ожидания. Мне интересно, если кто-то использует что-то подобное, я лично не могу представить программирование без него.

...