Shop OBEX P1 Docs P2 Docs Learn Events
Singleton pattern & share resoureces (Spinneret) — Parallax Forums

Singleton pattern & share resoureces (Spinneret)

agsags Posts: 386
edited 2011-03-06 06:48 in Accessories
This might not be the correct forum - and if so my apologies and moderators please do feel free to relocate as proper. I've chosen to post here as I'm building on another discussion in this forum related to having multiple cogs (here loosely called multiple threads) that need to share a common resource (in this case, the Spinneret).

The term "singleton" has been used in these threads, and I'm familiar with that design pattern in high-level languages (C++). How is it applied here? I understand that multiple instances of an object will each have their own VAR space, and shared DAT/PUB/PRI blocks. I understand that a single object that launches multiple cogs will result in each of the cogs sharing global symbols (RAM addresses) and shared DAT symbols if in SPIN, but separate DAT addresses if in PASM (in cog RAM). In the first case DAT symbols can be used to communicate across object instances, as VAR space will be unique to each instance. In the second case, the VAR symbols can be used to communicate between cogs.

When sharing a resource (SPI driving a WIZ5100 in this case) only one cog can have access to the resource at a time. Locks/mutex/semaphores seem in order. However, I've read in other threads (most recently, about sharing access to an SD card with an SPI driver) that singleton objects are involved. Is the lock object the singleton? Is the expectation that the programmer knows that only one singleton should be instantiated, by agreement/naming convention?

Q1: Am I on the right track here in my understanding (in other words, is the following question valid or misguided?)

Q2: How does one ensure that only one instance of an object is created? When I think of singleton that is what comes to mind. Although not appropriate in this context, I can't figure out "how do I declare a constructor private"?

Thanks.

Comments

  • Mike GMike G Posts: 2,702
    edited 2011-03-02 17:23
    Funny, I asked that question earlier on the Propeller forum.
    http://forums.parallax.com/showthread.php?130013-Question-PASM-COG-and-Static-methods
  • kuismakuisma Posts: 134
    edited 2011-03-02 23:52
    ags wrote: »
    Q2: How does one ensure that only one instance of an object is created? When I think of singleton that is what comes to mind. Although not appropriate in this context, I can't figure out "how do I declare a constructor private"?

    This is more a philosophical question, than a technical.

    You "create" and object instance in the OBJ section. If this "created" object do not contain any instance unique stuff what so ever (i.e. no VAR section), it is sharing all its resources (e.g. code, DAT declarations) with all other object instances in the same class (i.e. file). You can chose to see the two instances of the same class/file either as the same, or two different instances, as long as you not start using self modifying code or so. It doesn't really matter which.

    DAT sections in HUB memory is of course shared.

    DAT sections copied from HUB memory to a private COG address space with cognew ceases to be shared once no longer in HUB memory.


    Familiar with Java? See DAT variables as class variables, and VAR variables as instance variables.
  • Mike GMike G Posts: 2,702
    edited 2011-03-03 05:28
    I have a PASM object that uses pointers as arguments; input and output. So, as indicated in the Propeller Thread...

    In the main PASM object, create a public method that returns a pointer.
    Create another object that references the PASM object. Create a pointer method that exposes the main PASM object pointer.
    Instantiate the reference object in the objects that want to consume the main PASM
    You'll have some entry point object that will instantiate the main PASM and reference objects. The main PASM code is loaded in a COG.
    Now you have objects with pointers to the main PASM object. It is just a matter of pointer math to set/get the main PASM arguments.

    I'm creating a test rig so I can work out any bugs in my understanding. Hopefully I will have something later after work, just need to find the time.
  • agsags Posts: 386
    edited 2011-03-03 10:43
    kuisma wrote: »
    This is more a philosophical question, than a technical.
    ...
    Familiar with Java? See DAT variables as class variables, and VAR variables as instance variables.

    Yes, it is probably more methodology than technology. I only asked because it seemed to me that folks were speaking about the use of singletons as a method to deal with sharing resources across multiple cogs.

    Yes, I am quite familiar with Java and C++ and OOD.

    To get back on topic (didn't mean to get off-topic, of course) let me re-state the problem I'm tying to solve:

    Abstract: The WIZnet module (or any limited resource that cannot be "cloned" as needed for each cog) is a shared resource (like hub RAM). In my prototyping efforts, I've avoided dealing directly with ths by just mashing everything together at the top-object level in a single loop (similar to an event loop, with the events being data received on the WIZnet). To me, this is poor s/w design; it is not easily reusable, extensible, or efficent. Timothy has mentioned the idea of a "service layer" that would mange the shared resource (WIZnet) and provide services to different clients. I'm not sure how to do that.

    Some real-world examples of problems I'm facing:

    Roy Eltham created and shared some code (thanks, Roy) to implement a DHCP client. I used that as an example and created my own. It was a good learning experience. However, what I ended up with is not something that I could consider a true DHCP client. It will request a configuration from a DHCP server. But that's a one-time event. The lease is never renewed. If the DHCP server tries to push a renewal (for instance, say your LAN router is reconfigured to assign a different IP address to a particular MAC address) it is ignored. Once the initial configuration is received, the DHCP protocol is ignored until the next reboot. Some ways I can think of to deal with this are to check the DHCP lease in the main loop and renew when necessary (continuing the poor design I have, and requiring the top (parent) object to manage all the interaction with the WIZnet object) or to start a new cog for DHCP-related activity. For the latter, that would require instantiating another WIZnet object in the DHCP client object (causing conflicts with the other WIZnet object used by the HTTP server object, since they don't know about each other), or developing some way to control interaction with the WIZnet (semaphore, mutex, lock...). That was my entry point into this discussion. In C++/Java terms, if child objects are not aware of anything in the parent object, or siblings, how does one go about this synchronization?

    Another example of a real problem I'm facing: I'm trying to maximize data throughput from the WIZnet using the SPI driver. The reason to bother with this approach rather than just using the faster Indirect driver is to maximize output pins available to drive other devices with the data received from the WIZnet. (I've posted a question about the throughput of the WIZnet in Indirect mode but there have been no replies yet). Quick calculations show that if I'm able to streamline the processing and passing of data from the WIZnet to another cog bit-banging output pins, I may be able to reach my design goals while still using the SPI driver, thus allowing more output pins (and therefore devices) to be driven. To achieve the required efficiency, I'd need to have much more direct PASM-PASM (cog-cog) interaction. However, I also want to be able to have access to the WIZnet from other objects (say another object (or the parent object)) to provide a simple HTTP web server for stats/configuration/etc.

    I realize this is lengthy, but wanted to get back to the real problems (I have) at hand, and hope that the specific examples will help in doing so. Having said that, (briefly digressing back to a "philosophical" question) problems like this have led me to ask myself if this isn't a fundamental characteristic (and trade-off) of the Propeller's multi-cog design: Where resources can be duplicated, the idea that interrupts can be better handled by a cog running a dedicated method to service specific "events" seems reasonable. However, in the cases where a resource is scarce (like the WIZnet - there is only one, and it has to be shared) I wonder if the more "normal" concept of interrupts might not be more effective? I am hoping that those with greater experience and knowledge of the Propeller can offer insight that I'm missing here.

    Again, thanks for all the contributions and sharing of knowledge here. For me, that is a huge benefit of using the Propeller.
  • agsags Posts: 386
    edited 2011-03-03 10:50
    Mike G wrote: »
    I have a PASM object that uses pointers as arguments; input and output. So, as indicated in the Propeller Thread...

    In the main PASM object, create a public method that returns a pointer.
    Create another object that references the PASM object. Create a pointer method that exposes the main PASM object pointer.
    Instantiate the reference object in the objects that want to consume the main PASM
    You'll have some entry point object that will instantiate the main PASM and reference objects. The main PASM code is loaded in a COG.
    Now you have objects with pointers to the main PASM object. It is just a matter of pointer math to set/get the main PASM arguments.

    I'm creating a test rig so I can work out any bugs in my understanding. Hopefully I will have something later after work, just need to find the time.

    Yes, I reviewed that thread you pointed me at earlier and posted there also. Am I correct that in effect, what you are suggesting is that by using a symbol in a DAT block, and passing that address (which would be a hub RAM address) to each cog started (using that DAT block or not, actually) that is providing a single location that any number of cogs could use for messages? The advantage being that no matter how many times the object "owning" the DAT block is instantiated, there will be exactly one DAT block in hub RAM.

    Thanks.
  • kuismakuisma Posts: 134
    edited 2011-03-03 12:00
    ags,

    Have a look at my syslog example in another posting. It is a perfectly good example how different objects can simultaneous use the W5100 driver independent and unaware of each other.

    http://forums.parallax.com/showthread.php?129515-W5100-Sn_SR-undocumented-misfeature-bug&p=976729&viewfull=1#post976729

    E.g. you have two spin objects, foo and bar, independent. Both need logging and both refers syslog.spin unaware of each other. They will never mess up the driver since txUDP() is wrapped in between mutexLock/mutexRelease guaranteeing all calls to the driver are mutual exclusive.

    If you have other object also referring the driver, just wrap all calls to it with the mutex calls, and they will be safe as well.

    Note that in the syslog example, the socket is assumed to be initlialized, as well as the mutex semaphore. This you have to do *one* time, e.g. in your main object.
  • Mike GMike G Posts: 2,702
    edited 2011-03-03 18:20
    Yes, I reviewed that thread you pointed me at earlier and posted there also. Am I correct that in effect, what you are suggesting is that by using a symbol in a DAT block, and passing that address (which would be a hub RAM address) to each cog started (using that DAT block or not, actually) that is providing a single location that any number of cogs could use for messages? The advantage being that no matter how many times the object "owning" the DAT block is instantiated, there will be exactly one DAT block in hub RAM

    Dead nuts... with the understanding that a destination address can be destructive. Whomever invokes the running process must understand that memory can be overwritten. Plus the order of variable initialization is important. You don't want to issue a command before filling the arguments, at least in my PASM, the code is looking for a non-zero value, then rock and roll.

    The PASM object has this pattern.
    VAR
      long  cog, command, output
      long  source, destination
      long  offset, binary
    

    All VARs are handled as pointer by the PASM code. Load the memory addresses and wait for the result. The trick is creating another object that holds the starting address of some place in the VAR block of the PASM object, say command. From there you just need to add the object to a consumer, expose the pointer, and do pointer math. I still need to give it a try but it seems very reasonable.
  • agsags Posts: 386
    edited 2011-03-05 22:14
    Still tracking this thread (with great interest). Did you mean to have the code example in a VAR block or a DAT block? Based on the first half of the post, I expected an example of how multiple cogs could be started, all pointing to the same hub RAM location. Perhaps there was a different point you were making?

    If possible, code examples would (for my way of learning/communicating) be best to describe the concept.

    Thanks.
  • Mike GMike G Posts: 2,702
    edited 2011-03-06 06:48
    I have some time this morning but I promised my son I 'd take him hiking. Real nice here in Phoenix AZ. Need to take advantage before it's 110.

    Anyway, I'm creating a singleton object. The object is running in one COG started by the project's main entry point. That single process can be invoked by any method in any object in the project scope. The invoking objects all have reference objects to the singleton's VAR block. The VAR block holds memory pointers. The pointers are passed to the COG process. I need two objects for this to work. An object that does the processing and another that is reference, kinda like a structure with no implementation
Sign In or Register to comment.