preface
Some time ago, I found a pit when implementing business in Python. To be precise, it is easy for the Python layman to step on the pit.
The approximate code is as follows:
class Mom(object) :
name = ' '
sons = []
if __name__ == '__main__':
m1 = Mom()
m1.name = 'm1'
m1.sons.append(['s1'.'s2'])
print '{} sons={}'.format(m1.name, m1.sons)
m2 = Mom()
m2.name = 'm2'
m2.sons.append(['s3'.'s4'])
print '{} sons={}'.format(m2.name, m2.sons)
Copy the code
We define a Mom class that contains a string name and a list sons attribute.
An instance of m1 is created and a list is written to sons. An instance m2 is then created and another list data is written to sons.
If it’s a Javaer that rarely writes Python, the first output that comes to mind should be:
m1 sons=[['s1'.'s2']]
m2 sons=[['s3'.'s4']]
Copy the code
But the final output is:
m1 sons=[['s1'.'s2']]
m2 sons=[['s1'.'s2'], ['s3'.'s4']]
Copy the code
If you want to reach the expected value, you need to modify it slightly:
class Mom(object) :
name = ' '
def __init__(self) :
self.sons = []
Copy the code
All you need to do is change the class definition, and I’m sure you don’t have to have Python experience to compare the two codes to guess why:
In Python, if you want a variable as an instance variable (that is, every output we expect), you define the variable in the constructor, accessed by self.
If placed only in a class, the effect is similar to that of a Java static variable. The data is shared by the class, which explains why the first case occurs because the sons is shared by the Mom class, so it adds up each time.
Python singleton
Since Python can use class variables to achieve the effect that variables are shared within the same class, can you implement the singleton pattern?
You can use Python’s metaclass feature to dynamically control class creation.
class Singleton(type) :
_instances = {}
def __call__(cls, *args, **kwargs) :
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
Copy the code
First we create a base class for the Singleton, and then we use it as a Metaclass in the class we need to implement the Singleton
class MySQLDriver:
__metaclass__ = Singleton
def __init__(self) :
print 'MySQLDriver init..... '
Copy the code
This allows the Singleton to control the creation of the MySQLDriver class; In fact, __call__ in Singleton can easily understand the Singleton creation process:
- Define a private class attribute
_instances
The dictionary (i.eJava
In themap
) can be shared across the class, no matter how many instances are created. - When we use custom class
__metaclass__ = Singleton
After that, you can control the creation of custom classes; If an instance has already been created, then directly from_instances
Take the object and return it, or create an instance and write it back_instances
, a littleSpring
The feeling of the container.
if __name__ == '__main__':
m1 = MySQLDriver()
m2 = MySQLDriver()
m3 = MySQLDriver()
m4 = MySQLDriver()
print m1
print m2
print m3
print m4
MySQLDriver init.....
<__main__.MySQLDriver object at 0x10d848790>
<__main__.MySQLDriver object at 0x10d848790>
<__main__.MySQLDriver object at 0x10d848790>
<__main__.MySQLDriver object at 0x10d848790>
Copy the code
Finally, we can see the success of singleton creation through the experimental results.
Go the singleton
As part of the team has recently started using GO, I wanted to see how singletons could be implemented in GO.
type MySQLDriver struct {
username string
}
Copy the code
There is no way to declare class shared variables in such a simple structure as in Python and Java; The concept of static does not exist in go.
But we can declare a global variable in the package to achieve the same effect:
import "fmt"
type MySQLDriver struct {
username string
}
var mySQLDriver *MySQLDriver
func GetDriver(a) *MySQLDriver {
if mySQLDriver == nil {
mySQLDriver = &MySQLDriver{}
}
return mySQLDriver
}
Copy the code
Thus in use:
func main(a) {
driver := GetDriver()
driver.username = "cj"
fmt.Println(driver.username)
driver2 := GetDriver()
fmt.Println(driver2.username)
}
Copy the code
Instead of constructing MySQLDriver directly, use GetDriver(), and debug to see that driver and Driver1 refer to the same memory address.
There is nothing wrong with such a general implementation, and as any savvy friend can imagine, as with Java, it’s not that easy once concurrent access is available.
In Go, if more than one Goroutine accesses GetDriver() at the same time, multiple instances of MySQLDriver will most likely be created.
In contrast to Java, go provides a simple API for accessing critical resources.
var lock sync.Mutex
func GetDriver(a) *MySQLDriver {
lock.Lock()
defer lock.Unlock()
if mySQLDriver == nil {
fmt.Println("create instance......")
mySQLDriver = &MySQLDriver{}
}
return mySQLDriver
}
func main(a) {
for i := 0; i < 100; i++ {
go GetDriver()
}
time.Sleep(2000 * time.Millisecond)
}
Copy the code
Modify the above code slightly, add
lock.Lock()
defer lock.Unlock()
Copy the code
The code simply controls access to critical resources, and even if we enable 100 coroutines to execute concurrently, mySQLDriver instances are initialized only once.
- Here,
defer
Similar to theJava
In thefinally
Before the method callgo
Keyword to start a coroutine.
While this satisfies concurrency requirements, the implementation is not elegant enough; If you think about it here
mySQLDriver = &MySQLDriver{}
Copy the code
Creating an instance is called only once, but each subsequent call requires locking and incurs unnecessary overhead.
This scenario is the same for every language. How often do you see a singleton implementation like this in Java?
public class Singleton {
private Singleton(a) {}
private volatile static Singleton instance = null;
public static Singleton getInstance(a) {
if (instance == null) {
synchronized (Singleton.class){
if (instance == null) {
instance = newSingleton(); }}}returninstance; }}Copy the code
This is a typical double-checking singleton, where two checks are done to prevent subsequent threads from accessing the lock again.
The same is true for GO:
func GetDriver(a) *MySQLDriver {
if mySQLDriver == nil {
lock.Lock()
defer lock.Unlock()
if mySQLDriver == nil {
fmt.Println("create instance......")
mySQLDriver = &MySQLDriver{}
}
}
return mySQLDriver
}
Copy the code
As with Java, an additional judgment on top of the original can achieve the same effect.
Do you feel that such code is very cumbersome, this point is very easy to go API:
var once sync.Once
func GetDriver(a) *MySQLDriver {
once.Do(func(a) {
if mySQLDriver == nil {
fmt.Println("create instance......")
mySQLDriver = &MySQLDriver{}
}
})
return mySQLDriver
}
Copy the code
Essentially we only need to initialize the MySQLDriver instance once in any case to achieve singletons, so using once.do () makes the code execute only once.
If you look at the source code, you will find that once.Do() is also implemented through the lock, but before the lock using the underlying atomic operation to Do a check, so as to avoid locking every time, the performance is better.
conclusion
I believe that you will rarely encounter the need to implement a singleton in your daily development; First of all, most of the time we don’t need singletons, and even if we do, frameworks usually have integrations.
There are fewer frameworks like GO, and we don’t need to worry too much about concurrency when we need to implement them ourselves; Touch the upper left side of your stomach and think about it. Is it true that this object you wrote has hundreds or thousands of concurrent creation?
However, the comparison shows that go’s syntax is much cleaner than Java’s, while the lightweight coroutines and easy-to-use concurrency tool support look much more elegant than Java’s. We’ll have a chance to go further.
Reference links:
Creating a singleton in Python
How to implement Singleton Pattern in Go