⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 8trans.html

📁 C ++ in action
💻 HTML
📖 第 1 页 / 共 2 页
字号:
<html>
<head>

<title>Advanced Features of C++</title>
<link rel="stylesheet" href="../rs.css">
</head>
<body background="../images/margin.gif" bgcolor="#ffffe0">

<!-- Main Table -->
<table cellpadding="6">
    <tr>
    <td width="78">&nbsp;</td>
    <td>

<h3>Transactions</h3>


<P>Imagine using a word processor. You are editing a large document and, after working on it for several hours, you decide to save your work. Unfortunately, there is not enough disk space on your current drive and the save fails. What do you expect to happen? 
<P>Option number one is: the program gives up and exits. You are horrified--you have just lost many hours of work. You try to start the word processor again and you have a heart attack--the document is corrupted beyond recovery. Not only have you lost all recent updates, but you lost the original as well. 
<P>If horrors like this don't happen, it is because of transactions. A transaction is a series of operations that move the program from one well defined state to another. A transaction must be implemented in such a way that it either completely succeeds or totally fails. If it fails, the program must return to its original state.
<P>In any professionally written word processor, saving a document is a transaction. If the save succeeds, the on-disk image of the document (the file) is updated with the current version of the document and all the internal data structures reflect this fact. If the save fails, for whatever reason, the on-disk image of the documents remains unchanged and all the internal data structures reflect that fact. A transaction cannot succeed half-way. If it did, it would leave the program and the file in an inconsistent, corrupted, state. 
<P>Let's consider one more word-processing scenario. You are in the middle of editing a document when suddenly all lights go out. Your computer doesn't have a UPS (Uninterrupted Power Supply) so it goes down, too. Five minutes later, the electricity is back and the computer reboots. What do you expect to happen?
<P>The nightmare scenario is that the whole file system is corrupt and you have to reformat your disk. Of course, your document is lost forever. Unfortunately, this is a real possibility with some file systems. Most modern file systems, however, are able to limit the damage to a single directory. If this is not good enough for you (and I don't expect it is), you should look for a recoverable file system. Such systems can limit the damage down to the contents of the files that had been open during the crash. It does it by performing transactions whenever it updates the file system metadata (e.g., directory entries). Asking anything more from a file system (e.g., transacting all writes) would be impractical--it would slow down the system to a crawl.
<P>Supposing you have a recoverable file system, you should be able to recover the last successfully saved pre-crash version of your document. But what if the crash happened during the save operation? Well, if the save was implemented as a transaction it is guaranteed to leave the persistent data in a consistent state--the file should either contain the complete previously saved version or the complete new version. 
<P>Of course, you can't expect to recover the data structures that were stored in the volatile memory of your computer prior to the crash. That data is lost forever. (It's important to use the auto-save feature of your word processor to limit such losses). That doesn't mean that you can't or shouldn't transact operations that deal solely with volatile data structures. In fact, every robust program must use transactions if it is to continue after errors or exceptions. 
<P>What operations require transactions? 
<P>Any failure-prone action that involves updating multiple data structures might require a transaction.

<h4>Transient Transactions</h4>

<P>A transient, or in-memory, transaction does not involve any changes to the persistent (usually on-disk) state of the program. Therefore a transient transaction is not robust in the face of system crashes or power failures. 
<P>We have already seen examples of such transactions when we were discussing resource management. A construction of a well implemented complex data structure is a transaction--it either succeeds or fails. If the constructor of any of the sub-objects fails and throws an exception, the construction of the whole data structure is reversed and the program goes back to the pre-construction state (provided all the destructors undo whatever the corresponding constructors did). That's just one more bonus you get from using resource management techniques.
<P>There are however cases when you have to do something special in order to transact an operation. Let's go back to our word processor example. (By the way, the same ideas can be applied to the design of an editor; and what programmer didn't, at one time or another, try to write his or her own editor.) Suppose that we keep text in the form of a list of paragraphs. When the user hits return in the middle of a paragraph, we have to split this paragraph into two new ones. This operation involves several steps that have to be done in certain order:
<ul>
<li>Allocate one new paragraph.
<li>Allocate another new paragraph.
<li>Copy the first part of the old paragraph into the first new paragraph.
<li>Copy the second part of the old paragraph into the second new paragraph.
<li>Plug the two new paragraphs in the place of the old one.
<li>Delete the old paragraph.
</ul>
<P>The switch--when you plug in the new paragraphs--is the most sensitive part of the whole operation. It is performed on some master data structure that glues all paragraphs into one continuous body of the document. It is also most likely a dynamic data structure whose modifications might fail--the computer might run out of memory while allocating an extension table or a link in a list. Once the master data structure is updated, the whole operation has been successful. In the language of transactions we say "the transaction has committed." But if the crucial update fails, the transaction aborts and we have to unroll it. That means we have to get the program back to its original state (which also means that we refuse to split the paragraph). 
<P>What's important about designing a transaction is to make sure that 
<ul>
<li>All operations that proceed the commit are undoable in a safe manner (although the operations themselves don't have to--and usually aren't--safe).
<li>The commit operation is safe.
<li>All operations that follow it are also safe. 
</ul>
<p>Operations that involve memory allocation are not safe--they may fail, e.g., by throwing an exception. In our case, it's the allocation of new paragraphs that's unsafe. The undo operation, on the other hand, is the deletion of these paragraphs. We assume that deletion is safe--it can't fail. So it is indeed okay to do paragraph allocation before the commit.
<p>The commit operation, in our case, is the act of plugging in new paragraphs in the place of the old paragraph. It is most likely implemented as a series of pointer updates. Pointer assignment is a safe operation.
<p>The post-commit cleanup involves a deletion, which is a safe operation. Notice that, as always, we assume that destructors never throw any exceptions. 

<P>The best way to implement a transaction is to create a transaction object. Such an object can be in one of two states: committed or aborted. It always starts in the aborted state. If its destructor is called before the state is changed to committed, it will unroll all the actions performed under the transaction. Obviously then, the transaction object has to keep track of what's already been done--it keeps the <i>log</i> of actions. Once the transaction is committed, the object changes its state to committed and its destructor doesn't unroll anything. 
<P>Here's how one could implement the transaction of splitting the current paragraph. 

<!-- Code --><table width="100%" cellspacing=10><tr>	<td class=codetable>
<pre>void Document::SplitCurPara ()
{
    Transaction xact;
    Paragraph * para1 = new Paragraph (_curOff);
    xact.LogFirst (para1);
    Paragraph * para2 = new Paragraph (_curPara--&gt;Size () - _curOff);
    xact.LogSecond (para2);
    Paragraph * oldPara = _curPara;
    // May throw an exception!
    SubstCurPara (para1, para2);
    xact.Commit ();
    delete oldPara;
    // destructor of xact executed
}</pre>
</td></tr></table><!-- End Code -->


<P>This is how the transaction object is implemented.
<!-- Code --><table width="100%" cellspacing=10><tr>	<td class=codetable>
<pre>class Transaction
{
public:
    Transaction () : _commit (false), _para1 (0), _para2 (0) {}
    ~Transaction () 
    {
        if (!_commit)
        {
            // unroll all the actions
            delete _para2;
            delete _para1;
        }
    }
    void LogFirst (Paragraph * para) { _para1 = para; }
    void LogSecond (Paragraph * para) { _para2 = para; }
    void Commit () { _commit = true; }
private:
    bool        _commit;
    Paragraph * _para1;
    Paragraph * _para2;
};</pre>
</td></tr></table><!-- End Code -->


<P>Notice how carefully we prepare all the ingredients for the transaction. We first allocate all the resources and log them in our transaction object. The new paragraphs are now owned by the transaction. If at any point an exception is thrown, the destructor of the Transaction, still in its non-committed state, will perform a rollback and free all these resources.
<P>Once we have all the resources ready, we make the switch--new resources go into the place of the old ones. The switch operation usually involves the manipulation of some pointers or array indexes. Once the switch has been done, we can commit the transaction. From that point on, the transaction no longer owns the new paragraphs. The destructor of a committed transaction usually does nothing at all. The switch made the document the owner of the new paragraphs and, at the same time, freed the ownership of the old paragraph which we then promptly delete. All simple transactions follow this pattern:
<ul>
<li>Allocate and log all the resources necessary for the transaction.
<li>Switch new resources in the place of old resources and commit.
<li>Clean up old resources.
</ul>

<p><img src="images/Image51.gif" width=258 height=138 border=0 alt="Prepared transaction">
<P>Figure: Prepared transaction. The transaction owns all the new resources. The master data structure owns the old resources.

<p><img src="images/Image52.gif" width=258 height=138 border=0 alt="Aborted transaction">
<P>Figure: Aborting a transaction. The transaction's destructor frees the resources.

<p><img src="images/Image53.gif" width=258 height=138 border=0 alt="The switch">
<P>Figure 3--11 The switch. The master data structure releases the old resources and takes the ownership of the new resources.

<p><img src="images/Image54.gif" width=258 height=138 border=0 alt="Cleanup">
<P>Figure 3--12 The cleanup. Old resources are freed and the transaction is deleted.

<h4>Persistent Transactions</h4>

<P>When designing a persistent transaction--one that manipulates persistent data structures--we have to think of recovering from such disasters as system crashes or power failures. In cases like those, we are not so much worried about in-memory data structures (these will be lost anyway), but about the persistent, on-disk, data structures. 
<P>A persistent transaction goes through similar stages as the transient one.
<ul>
<li>Preparation: New information is written to disk.
<li>Commitment: The new information becomes current, the old is disregarded.
<li>Cleanup: The old information is removed from disk.
</ul>
<P>A system crash can happen before or after commitmentment (I'll explain in a moment why it can't happen during the commit). When the system comes up again, we have to find all the interrupted transactions (they have to leave some trace on disk) and do one of two things: if the transaction was interrupted before it had a chance to commit, we must unroll it; otherwise we have to complete it. Both cases involve cleanup of some on-disk data. The unrolling means deleting the information written in preparation for the transaction. The completing means deleting the old information that is no longer needed. 

<p><img src="images/Image55.gif" width=288 height=156 border=0 alt="Persistent switch">
<P>Figure: The Switch. In one atomic write the on-disk data structure changes its contents.

<P>The crucial part of the transaction is, of course, commitmentment. It's the "flipping of the switch." In one atomic operation the new information becomes current and the old becomes invalid. An atomic operation either succeeds and leaves a permanent trace on disk, or fails without leaving a trace. That shouldn't be difficult, you'd say. How about simply writing something to a file? It either succeeds or fails, doesn't it? 

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -