tech

[English] [Japanese]

81.1 Asynchronous processing (past information)

With the support of transcompiling from LuneScript to go language, asynchronous processing by LuneScript is now supported.

However, this is very experimental content, so please use it only as a reference.

Deprecated since 1.3.0. This document will be retained as a past log.

LuneScript runtime

LuneScript has a runtime to bridge the gap between the LuneScript language specification and the transcompiled language specification.

For example, when transcompiling to go, the runtime implements the following processing.

  • Working with lua
  • and or operator behavior

These operations are accessing data declared within the runtime.

When LuneScript supports asynchronous processing, it is necessary to support exclusive data access within the runtime.

On the other hand, exclusive control by mutex etc. affects performance even in single thread.

In this asynchronous process, in order to minimize the impact on a single thread, we minimized exclusive control, duplicated the necessary data, and switched the data to be accessed for each thread.

Specifically, the runtime data required for "cooperation with lua" and "behavior of and or operator" is duplicated for each thread and switched for each thread.

There is one problem here.

That is, go can't get the current running thread id.

If you can get the thread ID, you can get the current thread ID and switch the access destination to the runtime data corresponding to that thread ID when accessing the runtime data.

However, it is not possible because the thread ID cannot be obtained.

If you can't do this, you'll need a way to pass the thread ID to all function arguments.

I didn't want to do this, so I thought of another way.

In the following, I will explain the asynchronous processing method introduced this time.

Introduction and limitations of the LnsThread class

If you do asynchronous processing in LuneScript, inherit the LnsThread class.

The LnsThread class is a class like this:

// @lnsFront: skip
abstract class LnsThread {
   pro abstract fn loop() mut;
}

A subclass of LnsThread overrides the loop() method and does asynchronous processing within it.

When the following command is executed within a method of a subclass of LnsThread:

_lune_control run_async_pipe;

go routine loop() The method is executed.

In order to inherit LnsThread, the following declaration must be made in advance.

_lune_control use_async;

This is the command that declares that we are doing asynchronous processing.

Within the module in which this command is executed, the following operations are restricted and can only be executed from within methods of subclasses of LnsThread.

  • and/or operations
  • nil conditional operation
  • some built-in functions, method access

It also adds the __pipe<T> class as a means of sending and receiving data to and from asynchronous processes.

Think of the __pipe<T> class as equivalent to chan in go.

The __pipe<T> class is a class like this:

// @lnsFront: skip
class __pipe<T> {
  pub fn get() mut : T!;
  pub fn put( val:T! ) mut;
}

Here, T of __pipe<T> must be a class that implements the __AsyncItem interface.

The val obtained by put() can now be obtained by get(). If you give nil to put(val:T!), the communication of that __pipe will end, and after that even if put() is not nil, get() will return nil.

A class that implements the __AsyncItem interface must also implement the Mapping interface at the same time.

The following methods are added to classes that implement the __AsyncItem interface.

pub static fn _createPipe( num:int ) : __pipe<T>!;

where the argument num is the same as the cap of chan.

Note that this _createPipe() function always returns nil when transcompiled to Lua.

Summarizing the above, asynchronous processing is written as follows.

// @lnsFront: skip
_lune_control use_async;

class Test extend (__AsyncItem,Mapping) {
   let val:str {pub};
}

class Async extend LnsThread {
   let mut count:int {pub};
   let mut pipe:__pipe<Test>!;
   
   pub fn __init( pipe:__pipe<Test>! ) {
      super();
      self.pipe = pipe;
      self.count = 0;
   }
   
   pro override fn loop() mut {
      print( "hoge:" );
      while true {
         if! let mut pipe = self.pipe {
            let! val = pipe.get() {  // pipe からデータを取得
               print( "hoge:" );
               break;
            };
            val.$val.find( "%d" (self.count) ## );
            self.count = self.count + 1;
         }
      }
   }
   pub fn start() {
      _lune_control run_async_pipe;  // loop() 起動
   }
   pub fn put( test:Test ) mut {
      if! let mut pipe = self.pipe { // pipe にデータをセット
         pipe.put( test );
      }
   }
}

let mut async = new Async( Test._createPipe( 10 ) );
async.start();
let mut async2 = new Async( Test._createPipe( 10 ) );
async2.start();

for _ = 1, 100000 {
   async.put( new Test( "abcdefg" ) );
   async2.put( new Test( "abcdefg" ) );
}
print( async.$count, async2.$count );

Limitations of asynchronous processing

The behavior is undefined if the following processing is performed.

  • Access data and methods of another module from within Loop()
  • Access data and methods of a class that inherits LnsThread from another module
  • Access data and methods between instances of classes that inherit LnsThread

As mentioned above, it is a very limited and inconvenient specification. This specification will definitely change in the future, so please use it only as a reference as I wrote at the beginning.

By the way, although this asynchronous processing has many restrictions, it is used for LuneScript parse processing, and it contributes a little to the speedup of LuneScript.