CSC8016_Advanced Programming--Race Conditions and 2PL

Concurrent Programming

Something is concurrent if many different “things” are allowed to happen “at the same
time” (concurrēns).
Multiprogramming or Physical Concurrency
we mimic parallelism by running multiple processes on one single machine (interleaving).
Multiprocessing or Logical Concurrency
multiple processes are now run on multiple processors
如果允许许多不同的“事情”同时发生,时间”(concurrencies)。
多道程序设计或物理并发
我们通过在一台机器上运行多个进程(交错)来模拟并行性。
多处理或逻辑并发
多个进程现在在多个处理器上运行

At first, we might argue that multiprogramming and multiprocessing entail different types of problems.
On the contrary, they exhibit the same kind of problems, as we cannot predict the speed or the sequence on which the operations are going to be performed.
Therefore, we are going to use several different approaches for “forcing” process to exhibit a “proper” behaviour.

Multiprogramming

Multiprogramming is usually achieved in two different ways:
Data Parallelism: when the same operation is performed over distinct data, where each thread is accessing a distinct portion of it.
Operation Parallelism: when we apply multiple operations to the same data set, and each thread carries out a distinct operation.

Process

For the moment, you have been using Java by creating one single process, which is an instance of a program running (in this case) on the JVM.
Each process provides the resources needed to execute a program.
Each process has an isolated memory.
With the exception of POSIX’s shmem at the OS level!
Each process is started with a single “running component”, called thread.
– Thread.currentThread()
每个进程都提供执行程序所需的资源。

每个进程都有一个独立的内存。

Thread

A thread is a “running subset” of a process, with which it shares the same process memory and resources.
A process may also be made up of multiple threads of execution that execute instructions concurrently.
Synchronizing the access to the shared data is now relevant!
线程是进程的“运行子集”,它与进程共享相同的进程内存和资源。
进程也可以由并发执行指令的多个执行线程组成。
同步对共享数据的访问现在是相关的!
在这里插入图片描述

Shared Memory Synchronisation

mechanisms allowing to enforce a specific scheduling order.
The communication between two processes happens through a shared variable in memory.
To enforce a specific order, we need to exploit specific techniques to ensure that no conflict arise:
Through Atomic* classes ensuring that the operations over the shared variable are atomic (e.g., AtomicInteger).
Through synchronisation protocols prescribing the correct usage of constructs of the language (e.g., Strict 2PL).
允许执行特定调度顺序的机制。
两个进程之间的通信通过内存中的共享变量进行。
为了执行特定的命令,我们需要利用特定的技术来确保不发生冲突:
通过Atomic* 类,确保对共享变量的操作是原子的(例如,原子能)。
通过规定语言结构正确用法的同步协议(例如,严格2PL)。

Atomic Operations

An operation is atomic if it can run to completion in one go,or have no effect.
Simple assignments or read for data of a word size can be considered as atomic.
They don’t need to be executed necessarily in one CPU cycle(e.g., transaction)
Multiprogramming: the context switch between a process running an atomic operation must occur either before or after it, but never during it!
Multiprocessing: we need to guarantee that the action does not interfere with the other threads/processes.
如果一个操作可以一次完成,或者没有任何效果,那么它就是原子操作。
对于字大小的数据的简单赋值或读取可以被认为是原子的。
它们不需要在一个CPU周期内执行(例如,交易)
多道程序设计:运行原子操作的进程之间的上下文切换必须在它之前或之后发生,但绝不能在它期间发生!
多处理:我们需要保证操作不会干扰其他线程/进程。

Conflict/Data Race

Two operations in a schedule are said to conflict if they satisfy all three of the following conditions, that potentially leave the process into an inconsistent state:
they belong to different threads
they access the same shared variable
at least one operation is a write

Depending on when the read and the write operation occur, we could have three distinct types of conflicts:
Write-Read conflict
Read-Write conflict
Write-Write conflict

Violation

Lost Update: two threads accessing the same database items have their operations interleaved in a way that makes the value of some database items incorrect (Write-Write Conflict).

Dirty Read (or Temporary Update): reading a value that has been updated by a transaction that has not committed and that hence could abort later on (Write-Read Conflict)

Non-repeteable Read: a transaction T1 reads a value that is going to be updated by T2 prior to T1’s conflict (Read-Write Conflict)

Phantom (or Inconsistent Read): subsequent retrieval of the same set of unchanged objects from the transaction, give different results

丢失更新:访问相同数据库项的两个线程以使得某些数据库项的值不正确(写-写冲突)的方式交错它们的操作。

脏读(或临时更新):阅读一个值,该值已被尚未提交的事务更新,因此可能会在稍后中止(读写冲突)

不可重复读取:事务T1在T1的冲突(读写冲突)之前读取将由T2更新的值。

Phantom(或Inconsistent Read):后续从事务中检索同一组未更改的对象,给予不同的结果

Synchronisation

Interlevaing is required, but not all of the possible schedules must be allowed.
Some actions must be “rolled back” on abortion

Synchronisation is used to restrict the possible interleavings of process executions:

Mutual exclusion prevents certain statements in different processes from executing at the same time

Condition synchronisation enables processes to delay their execution until a particular condition is true

Appropriate use of synchronisation makes it possible to prevent interference by avoiding race conditions
在这里插入图片描述

Shared and Exclusive Locks

Locks are used to implement mutual exclusions. Locks might be defined over
single objects.
eXclusive locks, X(A):
If an object A is exclusively locked, shared locks cannot be obtained
If an object A is exclusively locked, other exclusive locks cannot be obtained.

Shared locks, S(A):
Multiple shared locks can co-exist.
If one or more shared locks over one object A already exist, exclusive locks cannot be obtained.
锁用于实现互斥。锁可以定义在单一对象。
专用锁,X(A):
如果对象A被独占锁定,则无法获得共享锁
如果一个对象A被独占锁定,则无法获得其他独占锁。

共享锁,S(A):
多个共享锁可以共存。
如果一个对象A上的一个或多个共享锁已经存在,则无法获得排他锁。

Strict Two Phase Locking (Strict 2PL)

Processes must ensure that there are no conflicts and aborted transactions:

Strict two-phase locking (Strict 2PL):
Write operation require use eXclusive locks, read ones use Shared locks.
The transaction unlocks all of its locks at commit time

Strict 2PL guarantees serializability:
If transactions handle different objects, they could be freely interleaved
If at least two transactions want to handle the same object, then such transactions has to be run serially.

The Strict Two Phase Locking protocol enforces the application of the following programming practice:

At each line of code of the thread accessing a variable x:
If x was already accessed in the past, do nothing
Otherwise, if x is either now or in the future accessed in writing, then put an exclusive lock on the variable before the operation on x,whichever that is now.
Otherwise, x will be always accessed on read, then put a shared lock on the variable before the current read operation.

At the end of the program, be it either commit or abort, release the locks in the reserved order of acquisition. Abort should also roll back the value to the variable as it was before the locking phase.
严格两阶段锁定协议强制应用以下编程实践:

在访问变量x的线程的每一行代码中:
如果x在过去已经被访问过,则什么也不做
否则,如果x现在或将来被写访问,则在x操作之前对变量设置一个排他锁,无论是现在还是将来。
否则,x将始终在读取时被访问,然后在当前读取操作之前对变量放置共享锁。

在程序结束时,无论是提交还是中止,都要按照保留的获取顺序释放锁。中止还应该将值回滚到变量,就像锁定阶段之前一样。

Strict 2PL in Java

Each thread will do: X(A),R(A),W(A),S(B),R(B),commit()

Process

final ReentrantReadWriteLock rwlA = new ReentrantReadWriteLock(),
rwlB = new ReentrantReadWriteLock();
volatile long A, B; // process shared variables

Thread #1 and #2
rwlA.writeLock().lock() // exclusive
localA = A; // some read and tmp copy
localA += 3; // some local write
rwlB.readLock().lock() // shared
localB = B; // some read operation
rwlB.readLock().unlock(); // commit…
A = localA; // at commit, before unlocking, updating the global
rwlA.writeLock().unlock(); // …!

AbortsAndCommit

var x = openTransaction() // Bank account: 30
x.withdraw(20); // now, it should be 10, but I haven’t committed yet.
x.abort(); // If any change is performed, the bank account should be
rolled back at 30. All of the operations are considered as ignored.
var x = openTransaction() // Bank account: 30
x.withdraw(20); // now, it should be 10, but I haven’t committed yet.
x.withdraw(20); // ineffective, as the bank account is 10
x.commit(); // The bank account is now committed, and therefore you
should have 2 successful operations.
Scenario: Optimistic Protocol
// Thread 1
var x = openTransaction() // Bank account: 30
x.withdraw(20); // now, it should be 10, but I haven’t committed yet.
x.withdraw(20); // ineffective, as the bank account is 10
// Thread 2
var y = openTransaction() // Same Bank account: maybe 30?
y.withdraw(20); // Maybe I can withdraw? I can try to do the operation, but I don’t
know whether to ignore it or not!
x.commit(); // Bank account is 10, no ignored operation
y.commit(); // the withdraw is not effective, y’s withdraw should be ignored,
and the bank account still be 10.
Scenario: Low Hanging Fruit
// Thread 1
var x = openTransaction() // Bank account: 30
x.withdraw(20); // now, it should be 10, but I haven’t committed yet.
x.withdraw(20); // ineffective, as the bank account is 10
// Thread 2
var y = openTransaction() // Same Bank account: maybe 30? So, I’m waiting for any other
thread to commit
x.commit(); // Bank account is 10, no ignored operation
y.withdraw(20); // ineffective, as the bank account is 10
y.commit(); // No operation is ignored at commit, because I have been
already informed the user that the withdraw was unsuccessful, so everything went as
expected

Java

import java.util.concurrent.locks.ReentrantReadWriteLock;
public class MyClass {
/// The the main part of the code required for the exam starts here
final ReentrantReadWriteLock rwlA = new ReentrantReadWriteLock();
final ReentrantReadWriteLock rwlB = new ReentrantReadWriteLock();
volatile long A, B;
public Thread generateThread(int i, boolean op) { // Using an explicit method is
not required…
return new Thread(() -> {
rwlA.writeLock().lock();
var localA = A;
localA = op ? (localA + i): (localA * i);
rwlB.readLock().lock();
var localB = B;
// commit! No need to set the localB, as this value was not updated
rwlB.readLock().unlock();
// commit! Updating the only variable that has been updated locally. Then,
releasing the lock
A = localA;
rwlA.writeLock().unlock();
});
}
/// The main part of the code required for the exam ends here! The rest is mainly
for you and running the code, but is not part of the exam.
public static void main(String[] args) throws InterruptedException {
var global = new MyClass();
for (int i = 0; i<5; i++) {
global.A = 0; global.B = 0; // Re-setting the globals
Thread t1 = global.generateThread(10, true), t2 = global.generateThread(25,
false);
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println(global.A+" vs "+global.B);
}
}
}

  • 23
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值