www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Static constructor

reply ludo <fakeaddress gmail.com> writes:
I read in the documentation
"Static constructors are used to initialize static class members 
with values that cannot be computed at compile time"

I try to understand the design of the following code:

---
class OpenAL
{
	static string[int] ALErrorLookup;
	static Object mutex;

	// Initialize static variables
	static this ()
	{	ALErrorLookup = [
			0xA001: "AL_INVALID_NAME"[],
			0xA002: "AL_ILLEGAL_ENUM",
			0xA002: "AL_INVALID_ENUM",
			0xA003: "AL_INVALID_VALUE",
			0xA004: "AL_ILLEGAL_COMMAND",
			0xA004: "AL_INVALID_OPERATION",
			0xA005: "AL_OUT_OF_MEMORY"
		];
		mutex = new Object();
	}

        static anotherfunc()
        {}

	static Object getMutex()
	{	return mutex;
         }
}
---

At this point, I have not looked up Object, guess must be a 
class. It seems to me that ALErrorLookup can be computed at 
compile time... So the constructor is static because mutex "can 
not be computed at compiled time"?

The idea of the dev (from context) is to that this class will 
just be a wrapper, no instance necessary. So anotherfunc(), which 
does the main work, is static and everything goes this way.

Then getMutex returns the static mutex when necessary... Have not 
looked that up yet either.

But, I don't know, i have a feeling that this is over 
complicated. For example, can't we have AlErrorlook-up 
initialized another way in D, a static mutex in the getMutex 
function directly (with if (mutex == null) {mutex = new Object()} 
.

I don't know, is it the proper D way? And also, when we have 
those classes with everything static, does it even make sense to 
have a class? This module actually contains only this class 
(https://tinyurl.com/yxt2xw23) Shouldn't we have one module with 
normal functions?

ANY input is learning material for me. Thanks.
Jan 06 2021
next sibling parent Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Wednesday, 6 January 2021 at 17:05:02 UTC, ludo wrote:
 I read in the documentation
 "Static constructors are used to initialize static class 
 members with values that cannot be computed at compile time"

 [...]
Since this is not the complete code it's a bit hard to know, but I'd guess this is for use as a singleton and/or for thread safety.
Jan 06 2021
prev sibling next sibling parent reply SealabJaster <sealabjaster gmail.com> writes:
On Wednesday, 6 January 2021 at 17:05:02 UTC, ludo wrote:
 ...
Using a static class like this seems to mostly be a design decision. i.e. instead of using something like ``` openalGetMutex(); // OR OpenAL openal = ...; openal.getMutex(); // OR I guess even this g_openal.getMutex(); ``` They specifically want the interface to look like: ``` OpenAL.getMutex(); ``` So in otherwords, this class is essentially being used as a unique namespace for all of the other functions. Another way to look at it is just a singleton without a `.instance` getter. As for some of the other stuff, Associative Array literals in D can't actually be used at compile-time (at least, last time I tried), so they have to be created inside of the static constructor. Next is the mutex. `Object` is the base class that every class in has a `.monitor` field which is basically just a mutex, so is used with multi-threading synchronisation. I find it a bit odd that they're just returning an object instead of a Mutex (module core.sync.mutex), but I'm sure there's a reason why. (The README of this project states that this is a resurrection of some old project, and even refers to D as "Digital Mars", so this code may or may not have been built on a very old version of D originally.) A better explanation of the monitor field can be found: https://forum.dlang.org/post/op.vqi9vrqueav7ka steve-laptop Hope this answers some questions.
Jan 06 2021
next sibling parent SealabJaster <sealabjaster gmail.com> writes:
On Thursday, 7 January 2021 at 01:55:07 UTC, SealabJaster wrote:
 ...
And on a side note, I don't believe this code is working as the author intends. Someone correct me if I'm wrong, but `static` variables in D arethread-local, so a `static Object mutex` wouldn't actually be visible between threads unless passed via some other means, and would instead act as a mutex-per-thread, sort of defying the point? However `__gshared Object mutex` would likely be what they want? I don't do much multithreaded stuff in D, so that information may or may not be correct.
Jan 06 2021
prev sibling parent reply ludo <fakeaddress gmail.com> writes:
NOTE : the entire code we are talking about is in the tiny url in 
my previous post.

On Thursday, 7 January 2021 at 01:55:07 UTC, SealabJaster wrote:
 On Wednesday, 6 January 2021 at 17:05:02 UTC, ludo wrote:
 ...
Using a static class like this seems to mostly be a design decision. So in otherwords, this class is essentially being used as a unique namespace for all of the other functions. Another way to look at it is just a singleton without a `.instance` getter.
Ok, I agree that ends up being a kind of strange singleton. But yes it was D v1 code. Do we agree that the following multi-threaded singleton pattern is the proper way as of today, instead of this static functions in a non static class (in multithreaded environment)? ---- shared(T) singleton() { static shared T instance; if(!instance) { synchronized { if(!instance) instance = new shared(T)(); } } return instance; } ---
 As for some of the other stuff, Associative Array literals in D 
 can't actually be used at compile-time (at least, last time I 
 tried), so they have to be created inside of the static 
 constructor.
OK, I found https://dlang.org/spec/hash-map.html#static_initialization Quote from that doc: "Static Initialization of AAs NOTE: Not yet implemented. " If I understand, as of today an AA init depends on a runtime function for historical reasons. A bit weird for an array indeed perfectly known at compile time, but someday some core language contributor will have a look at it, I guess.
 Next is the mutex. `Object` is the base class that every class 

 Object has a `.monitor` field which is basically just a mutex, 
 so is used with multi-threading synchronisation. I find it a 
 bit odd that they're just returning an object instead of a 
 Mutex (module core.sync.mutex), but I'm sure there's a reason 
 why.
If we consider the class from my first post, the comment coming with the getMutex func is following in the original code that I am "cleaning-up": "Get an OpenAL mutex to ensure that no two threads ever execute OpenAL functionality simultaneously." But the OpenAL class only has one function (anotherFunc), and as you say SealabJaster, it looks odd to use this Object mutex. I see two options to replace this mutex thing: * make the entire class synchronized (writing "synchronized class OpenAL") * make the anotherFunc function synchronized. So just one keyword to add! If I understand well, this will accomplish the goal quoted with no further comestics needed! Only one keyword over explicitely using object mutex. Am I right? Thanks
Jan 12 2021
parent reply SealabJaster <sealabjaster gmail.com> writes:
On Tuesday, 12 January 2021 at 11:28:12 UTC, ludo wrote:
 Ok, I agree that ends up being a kind of strange singleton. But 
 yes it was D v1 code. Do we agree that the following 
 multi-threaded singleton pattern is the proper way as of today.
It looks fine to me. The D wiki has the following example which prevents the need for entering the synchronized block more than once per thread: https://wiki.dlang.org/Low-Lock_Singleton_Pattern I should note that the example doesn't specify the value as shared, which technically isn't the correct thing to do, but `shared` itself can be a bit of an annoyance. However, you do need to ensure that your static variable is either shared or __gshared, otherwise it becomes thread-local.
 If I understand, as of today an AA init depends on a runtime 
 function for historical reasons. A bit weird for an array 
 indeed perfectly known at compile time, but someday some  core 
 language contributor will have a look at it, I guess.
Maybe in 5 years' time we'll get an uneventful discussion on it where it'd take 2 years to come to a conclusion before another year for it to actually be implemented.
 If I understand well, this will accomplish the goal quoted with 
 no further comestics needed! Only one keyword over explicitely 
 using object mutex. Am I right?
I believe so. I've never used OpenAL so it may have additional restrictions with multithreading, but from a simple "This function is only ever executed on one thread at a time", your above suggestions should work. Apologies for the late reply.
Jan 14 2021
parent reply ludo <fakeaddress gmail.com> writes:
 I believe so. I've never used OpenAL so it may have additional 
 restrictions with multithreading, but from a simple "This 
 function is only ever executed on one thread at a time", your 
 above suggestions should work.

 Apologies for the late reply.
No worry and thank you. I found the low-lock pattern on my own, digging more info. Smart pattern! I put the synchronized function in front of the function. The problem with multithreading, is the difficulty to verify that it works fine :) But now the topic streams away from static constructor :) Cheers
Jan 15 2021
parent Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Friday, 15 January 2021 at 16:04:02 UTC, ludo wrote:
 I believe so. I've never used OpenAL so it may have additional 
 restrictions with multithreading, but from a simple "This 
 function is only ever executed on one thread at a time", your 
 above suggestions should work.

 Apologies for the late reply.
No worry and thank you. I found the low-lock pattern on my own, digging more info. Smart pattern! I put the synchronized function in front of the function. The problem with multithreading, is the difficulty to verify that it works fine :) But now the topic streams away from static constructor :) Cheers
Isn't that kind of the de facto standard? Well, saw this was 7 years ago now 😁
Jan 15 2021
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/6/21 12:05 PM, ludo wrote:
 I read in the documentation
 "Static constructors are used to initialize static class members with 
 values that cannot be computed at compile time"
 
 I try to understand the design of the following code:
 
 ---
 class OpenAL
 {
      static string[int] ALErrorLookup;
      static Object mutex;
 
      // Initialize static variables
      static this ()
      {    ALErrorLookup = [
              0xA001: "AL_INVALID_NAME"[],
              0xA002: "AL_ILLEGAL_ENUM",
              0xA002: "AL_INVALID_ENUM",
              0xA003: "AL_INVALID_VALUE",
              0xA004: "AL_ILLEGAL_COMMAND",
              0xA004: "AL_INVALID_OPERATION",
              0xA005: "AL_OUT_OF_MEMORY"
          ];
          mutex = new Object();
      }
 
         static anotherfunc()
         {}
 
      static Object getMutex()
      {    return mutex;
          }
 }
 ---
 
 At this point, I have not looked up Object, guess must be a class. It 
 seems to me that ALErrorLookup can be computed at compile time... So the 
 constructor is static because mutex "can not be computed at compiled time"?
Associative arrays can be computed at compile time, and used at compile time. BUT they cannot be transferred to runtime AAs (yet). So likely that is the reason the AA initialization is in there. You can (now) create classes at compile-time, and then assign them to variables as static initializers. I don't know if that means you can create the mutex at compile-time. I know OS mutex primitives can be initialized at compile time. I don't know if that works properly here. Also note that what is happening here is not correct in D2 (I see that it's D1 you noted in a subsequent message), as `static this()` is run once per thread (and mutex is going to get one instance per thread). So most likely you need to change both of these to shared.
 
 The idea of the dev (from context) is to that this class will just be a 
 wrapper, no instance necessary. So anotherfunc(), which does the main 
 work, is static and everything goes this way.
 
 Then getMutex returns the static mutex when necessary... Have not looked 
 that up yet either.
It doesn't make a whole lot of sense, since mutex is publicly accessible.
 But, I don't know, i have a feeling that this is over complicated. For 
 example, can't we have AlErrorlook-up initialized another way in D, a 
 static mutex in the getMutex function directly (with if (mutex == null) 
 {mutex = new Object()} .
No on the AA (as noted above). The mutex *is* created on demand. Every Object can have a mutex, and it's only created when you synchronize it for the first time.
 I don't know, is it the proper D way? And also, when we have those 
 classes with everything static, does it even make sense to have a class? 
 This module actually contains only this class 
 (https://tinyurl.com/yxt2xw23) Shouldn't we have one module with normal 
 functions?
 
 ANY input is learning material for me. Thanks.
I would say the AA initialization is standard D. Using the class as a namespace isn't standard or necessary. If anything, it should be a struct, or you can use a template. But I can't see why you can't just use a module. -Steve
Jan 15 2021
parent reply ludo <fakeaddress gmail.com> writes:
Thanks, as explained I am indeed porting old code.

 No on the AA (as noted above). The mutex *is* created on 
 demand. Every Object can have a mutex, and it's only created 
 when you synchronize it for the first time.
Yes alright. I think the dev made a design mistake because he called synchronized( OpenAL.mutex ) when it should be more of a global, non OpenAL specific, mutex. I mean in the code there were things like (pseudo-code) --- System { private int[] buffer func() { synch (openal.mutex) { openal.dosmthg (buffer) buffer.change() // buffer being the member of System openal.dosmthg(buffer) } } } --- Basically, it's the entire buffers handling ( the 3 statements) which should be done the "atomic" way, not just the calls to openAL. So the mutex should be a member variable of System at worse. I just replaced synchronized ( openal.mutex ) { by synchronized { // create a global mutex Of course synchronized (open.mutex) would probably work, but getting the mutex of an abstract class which is used only in part of the calls looks like a design error to me!
 I would say the AA initialization is standard D. Using the 
 class as a namespace isn't standard or necessary. If anything, 
 it should be a struct, or you can use a template. But I can't 
 see why you can't just use a module.
I replaced the AA entirely (by a switch in a nested function!). Got rid of the static init() then. I replaced 'class' by 'struct', which does not oblige me to change the entire code using the syntax "OpenAL.smthg" . But I was tempted to go to the next step: import OpenAL = ...openal; // should not have to change any code either if it works! I just have to verify all the imports in the other modules and I can delete the "struct OpenAL" scope. I don't have enough experience in D2 to think about the impact of such a change though, ie from ***** --- // openal.d file module a.b.OpenAL struct OpenAL{ void dosmthg()} // another file import a.b.OpenAL OpenAL.dosmthg() --- ******** to ******* --- // openal.d file module a.b.OpenAL void dosmthg() // another file import OpenAL = a.b.OpenAL OpenAL.dosmthg() --- Thanks again for all the info so far. Learning a lot.
Jan 17 2021
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/17/21 6:54 PM, ludo wrote:
 Yes alright. I think the dev made a design mistake because he called 
 synchronized( OpenAL.mutex ) when it should be more of a global, non 
 OpenAL specific, mutex. I mean in the code there were things like  
 (pseudo-code)
 ---
 System {
    private int[] buffer
 
    func() {
      synch (openal.mutex)
      {
        openal.dosmthg (buffer)
        buffer.change() // buffer being the member of System
        openal.dosmthg(buffer)
      }
    }
 }
 ---
 Basically, it's the entire buffers handling ( the 3 statements) which 
 should be done the "atomic" way, not just the calls to openAL. So the 
 mutex should be a member variable of System at worse. I just replaced
 synchronized ( openal.mutex )  {
 by
 synchronized { // create a global mutex
 
 Of course synchronized (open.mutex) would probably work, but getting the 
 mutex of an abstract class which is used only in part of the calls looks 
 like a design error to me!
Not sure if this is right. If the mutex should be protecting *more* then you should move the scope of the mutex out of the object into module space (and make it shared so it actually is used across threads). Your code there allocates a mutex for that specific block, and nothing else. The mutex isn't more "global", it's more "local" (although, this will make it shared). I'd follow some rules like: 1. If the mutex is for one specific block and does not need to be locked for any other code, then use a naked synchronized command. This is an odd case, because it is for a specific block of code, but works across all calls of that code. So the data it protects needs to be only used in that code, but also used across all instances of the objects. I'd use this one rarely. 2. If the mutex is for data in a specific object, make it a member of that object (or use the object itself as the mutex). 3. If the mutex is for static data in an object that is shared between threads, make it a shared static member of that object. 4. If the mutex is for static data in multiple objects, then put it in the module as a shared data item. -Steve
Jan 18 2021
parent ludo <fakeaddress gmail.com> writes:
 The mutex isn't more "global", it's more "local" (although, 
 this will make it shared).
Yes shared, this is what I meant by global :) Thanks, it's clearer now.
Jan 18 2021