`

Ownership hierarchy using owner::this

阅读更多

Ownership hierarchy using owner::this


In my last post I talked about the proposal for the ownership scheme for multithreaded programs that provides alias control and eliminates data races. The scheme requires the addition of new type qualifiers to the (hypothetical) language. The standard concern is that new type qualifiers introduce code duplication. The classic example is the duplication of getters required by the introduction of the const modifier:

class Foo {
    private Bar _bar;
public:
    Bar get() {
        return _bar;
    }
    const Bar get() const {
        return _bar;
    }
}

Do ownership annotations lead to the same kind of duplication? Fortunately not. It’s true that, in most cases, two implementations of each public method are needed–with and without synchronization–but this is taken care by the compiler, not by the programmer. Unlike in Java, we don’t need a different class for shared Vector and thread-local ArrayList. In my scheme, when a vector is instantiated as a monitor (shared), the compiler automatically puts in the necessary synchronization code.

Need for generic code

The ownership scheme introduces an element of genericity by letting the programmer specify ownership during the instantiation of a class (just as a template parameter is specified during the instantiation of a template).

I already mentioned that most declarations can be expressed in two ways: one using type qualifiers, another using template notation–the latter exposing the generic nature of ownership annotations. For instance, these two forms are equivalent:

auto foo2 = new shared Foo;
auto foo2 = new Foo<owner::self>;

The template form emphasizes the generic nature of ownership annotations.

With the ownership system in place, regular templates parametrized by types also gain an additional degree of genericity since their parameters now include implicit ownership information. This is best seen in objects that don’t own the objects they hold. Most containers have this property (unless they are restricted to storing value types). For instance, a stack object might be thread-local while its elements are either thread-local or shared. Or the stack might be shared, with shared elements, etc. The source code to implement such stacks may be identical.

The polymorphic scheme and the examples are based on the GRFJ paper I discussed in a past post.

An example

- Stack

A parameterized stack might look like this :

class Stack<T> {
    private Node<T> _top;
public:
    void push(T value) {
        auto newNode = new Node<owner::this, T>;
        newNode.init(:=value, _top);
        _top = newNode;
    }
    T pop() {
        if (_top is null) return null;
        auto value := _top.value();
        _top = _top.next();
        return value;
    }
}

This stack is parametrized by the type T. This time, however, T includes ownership information. In particular T could be shared, unique, immutable or–the default–thread-local. The template picks up whatever qualifier is specified during instantiation, as in:

auto stack = new Stack<unique Foo>;

The _top (the head of the linked list) is of the type Node, which is parametrized by the type T. What is implicit in this declaration is that _top is owned by this–the default assignment of ownership for subobjects. If you want, you can make this more explicit:

private Node<owner::this, T> _top;

Notice that, when constructing a new Node, I had to specify the owner explicitly as this. The default would be thread-local and could leak thread-local aliases in the constructor. It is technically possible that the owner::this could, at least in this case, be inferred by the compiler through simple flow analysis.

Let’s have a closer look at the method push, where some interesting things are happening. First push creates a new node, which is later assigned to _top. The compiler cannot be sure that the Node constructor or the init method don’t leak aliases. That looks bad at first glance because, if an alias to newNode were leaked, that would lead to the leakage of _top as well (after a pop).

And here’s the clincher: Because newNode was declared with the correct owner–the stack itself–it can’t leak an alias that has a different owner. So anybody who tries to access the (correctly typed) leaked alias would have to hold the lock on the stack. Which means that, if the stack is shared, unsynchronized access to any of the nodes and their aliases is impossible. Which means no races on Nodes.

I also used the move operator := to move the values stored on the stack. That will make the stack usable with unique types. (For non-unique types, the move operator turns into regular assignment.)

I can now instantiate various stacks with different combinations of ownerships. The simplest one is:

auto localStack = new Stack<Foo>;

which is thread-local and stores thread-local objects of class Foo. There are no restrictions on Foo.

A more interesting combination is:

auto localStackOfMonitors = new Stack<shared Foo>;

This is a thread-local stack which stores monitor objects (the opposite is illegal though, as I’ll explain in a moment).

There is also a primitive multithreaded message queue:

auto msgQueue = new shared Stack<shared Foo>;

Notice that code that would try to push a thread-local object on the localStackOfMonitors or the msgQueue would not compile. We need the rich type system to be able to express such subtleties.

Other interesting combinations are:

auto stackOfImmutable = new shared Stack<immutable Foo>;
auto stackOfUnique = new shared Stack<unique Foo>;

The latter is possible because I used move operators in the body of Stack.

- Node

Now I’ll show you the fully parameterized definition of Node. I made all ownership annotations explicit for explanatory purposes. Later I’ll argue later that all of them could be elided.

class Node<T> {
private:
    T _value;
    Node<owner::of_this, T> _next;
public:
    void init(T v, Node<owner::of_this, T> next)
    {
        _value := v;
        _next = next;
    }
    T value() {
        return :=_value;
    }
    Node<owner::of_this, T> next() {
        return _next;
    }
}

Notice the declaration of _next: I specified that it must be owned by the owner of the current object, owner::of_this. In our case, the current object is a node and its owner is an instance of the Stack (let’s assume it’s the self-owned msgQueue).

This is the most logical assignment of ownership: all nodes are owned by the same stack object. That means no ownership conversions need be done, for instance, in the implementation of pop. In this assignment:

_top = _top.next();

the owner of _top is msgQueue, and so is the owner of its _next object. The types match exactly. I drew the ownership tree below. The fat arrows point at owners.
Ownership hierarchy using owner::of_this

But that’s not the only possibility. The default–that is _next being owned by the current node–would work too. The corresponding ownership tree is shown below.

Ownership hierarchy using owner::this

The left-hand side of the assignment

_top = _top.next();

is still owned by msgQueue. But the _next object inside the _top is not. It is owned by the _top node itself. These are two different owners so, during the assignment, the compiler has to do an implicit ownership conversion. Such conversion is only safe if both owners belong to the same ownership tree (sometimes called a “region”). Indeed, ownership is only needed for correct locking, and the locks always reside at the top of the tree (msgQueue in this case). So, after all, we don’t need to annotate _next with the ownership qualifier.

The two other annotations can be inferred by the compiler (there are some elements of type inference even in C++0x and D). The argument next to the method init must be either owned by this or be convertible to owner::this because of the assignment

_next = next;

Similarly, the return from the method next is implicitly owned by this (the node). When it’s used in Stack.pop:

_top = _top.next();

the owner conversion is performed.

With ownership inference, the definition of Node simplifies to the following:

class Node<T> {
private:
    T _value;
    Node<T> _next; // by default owned by this
public:
    void init(T v, Node<T> next)
    {
        _value := v;
        _next = next; // inference: owner of next must be this
    }
    T value() {
        return :=_value;
    }
    Node<T> next() {
        return _next; // inference: returned node is owned by this
    }
}

which has no ownership annotations.

Let me stress again a very important point: If init wanted to leak the alias to next, it would have to assign it to a variable of the type Node<owner::this, T>, where this is the current node. The compiler would make sure that such a variable is accessed only by code that locks the root of the ownership tree, msgQueue. This arrangement ensures the absence of races for the nodes of the list.

Another important point is that Node contains _value of type T as a subobject. The compiler will refuse instantiations where Node’s ownership tree is shared (its root is is self-owned), and T is thread-local. Indeed, such instantiation would lead to races if an alias to _value escaped from Node. Such an alias, being thread-local, would be accessible without locking.

Comment on notation

In general, a template parameter list might contain a mixture of types, type qualifiers, values, (and, in D, aliases). Because of this mixture, I’m using special syntax for ownership qualifiers, owner::x to distinguish them from other kinds of parameters.

As you have seen, a naked ownership qualifier may be specified during instantiation. If it’s the first template argument, it becomes the owner of the object. Class templates don’t specify this parameter, but they have access to it as owner::of_this.

Other uses of qualifier polymorphism

Once qualifier polymorphism is in the language, there is no reason not to allow other qualifiers to take part in polymorphism. For instance, the old problem of having to write separate const versions of accessors can be easily solved:

class Foo {
    private Bar _bar;
    public mut_q Bar get<mutability::mut_q>() mut_q
    {
        return _bar;
    }
}

Here method get is parametrized by the mutability qualifier mut_q. The values it can take are: mutable (the default), const, or immutable. For instance, in

auto immFoo = new immutable Foo;
immutable Bar b = immFoo.get();

the immutable version of get is called. Similarly, in

void f(const Foo foo) {
    const Bar b = foo.get();
}

the const version is called (notice that f may also be called with an immutable object–it will work just fine).

Class methods in Java or D are by default virtual. This is why, in general, non-final class methods cannot be templatized (an infinite number of possible versions of a method would have to be included in the vtable). Type qualifiers are an exception, because there is a finite number of them. It would be okay for the vtable to have three entries for the method get, one for each possible value of the mutability parameter. In this case, however, all three are identical, so the compiler will generate just one entry.

Conclusion

The hard part–explaining the theory and the details of the ownership scheme–is over. I will now switch to a tutorial-style presentation that is much more programmer friendly. You’ll see how simple the scheme really is in practice.

分享到:
评论

相关推荐

    ownership:Rails 的代码所有权

    所有权Rails 的代码所有权查看以获取其他提示 :tangerine: 在进行了测试安装将此行添加到您的应用程序的Gemfile中: gem 'ownership'入门所有权提供了为代码库的不同部分指定所有者的能力。 我们强烈建议所有者是...

    Take Ownership, Excel!:-开源

    - 帮助学习拥有所有权和建立责任感的视觉工具 - 可定制的视觉记分卡。 帮助自我识别优势和劣势 - 照亮选择以帮助授权 重要:请阅读许可条款(在安装目录中的“许可... 您使用软件程序即表示您接受许可条款和用户协议。

    Agile.Product.Ownership.2nd.Edition.1530399882.epub

    The purpose of this book is to better clarify the role of Agile Product Owner. This information is valuable to anyone who is taking on the role of product owner, as well as anyone involved in an Agile...

    git fatal detected dubious ownership in repository 的解决方法.rar

    开发案列优质学习资料资源工具与案列应用场景开发文档教程资料

    Development of Private Vehicle Ownership among Provinces in China: A Panel Data Analysis

    基于面板数据的省私人汽车拥有量的差异化发展研究,王俊达,Patrick McCarthy,近年来,随着经济的快速发展,私人汽车拥有量同样得到了飞速的增长。然而,车辆增长在给人们生活工作带来了许多便利的同时,也产生�

    take_ownership

    "take_ownership" 是一个针对Windows操作系统的实用工具,主要用于帮助用户轻松地获取对文件或文件夹的所有权。在Windows 7系统中,有时由于权限限制,用户可能无法直接修改或删除某些文件,这时"take_ownership...

    Take Ownership

    "Take Ownership"是一个Windows操作系统中的实用工具,它允许用户获取对特定文件或文件夹的所有权,以便能够修改其访问权限并进行操作。在默认情况下,某些系统文件或受保护的文件可能不允许用户直接进行编辑、删除...

    TakeOwnership

    "TakeOwnership"是一种解决方案,它允许用户获取文件或文件夹的所有权,以便能够完全控制这些资源。这个工具通常适用于需要高级权限才能操作的系统组件,或者由于安全设置而受到限制的用户。 在Windows 7中,"Take...

    TakeOwnership_registryeditor_ContextMenu_ownership开发_

    标题 "TakeOwnership_registryeditor_ContextMenu_ownership开发_" 暗示了这个压缩包涉及的是一个Windows系统中的功能增强,特别是关于文件和文件夹所有权的管理。这个功能允许用户通过右键菜单快速获得某个文件或...

    Add Take Ownership Option\Add Take Ownership Option.reg

    Add Take Ownership Option\Add Take Ownership Option.reg 取得管理员权限

    Add Take Ownership增加管理员权限.

    "Add Take Ownership"功能是为了帮助用户获取对系统文件或文件夹的所有权,以便在没有默认管理员权限的情况下进行操作。这个功能特别适用于那些由于权限限制无法访问或修改的文件。 标题中的"Add Take Ownership"指...

    Add_Take_Ownership

    Add_Take_Ownership

    ownership-status:所有权状态页

    所有权状态页面 我们建议在安装python, 和其他依赖项之前在工作: $ make makerules $ make init $ make 每夜 这些页面由。 执照 该项目中的软件是开源的,并包含在文件中。... 复制到该存储库中的各个数据集可能具有...

    Pet Ownership Statistics (POS) SystemCOMP9103软件开发作业、Java编程程序作业、Java语言编程作业

    This INDIVIDUAL software development assignment consists of THREE PARTS, and is worth 22% of the assessment of this course. In this individual assignment, you will develop Java ...ownership management.

    使用ansible安装zookeeper

    - name: Set ownership and permissions file: path: "{{ item }}" owner: zookeeper group: zookeeper mode: 0755 with_items: - /opt/apache-zookeeper-3.7.0-bin ``` 5. **启动Zookeeper**: 定义一个...

    将文件转换为rust格式的二进制文件

    2. 所有权和借用(Ownership & Borrowing):Rust采用了所有权模型,即每个值都有一个唯一的所有者。所有者负责释放值所占用的内存。通过借用(borrowing)机制,Rust允许在不转移所有权的情况下临时访问值。 3. ...

    Take ownership

    "Take ownership" 是一个在Windows操作系统中经常遇到的概念,它涉及到文件权限管理。当你尝试访问或修改某个文件或文件夹时,系统可能会因为权限限制而阻止你操作,这时候就需要“取得所有权”来获取对该资源的完全...

Global site tag (gtag.js) - Google Analytics