如何在多进程和多线程环境中生成随机唯一标识符?

时间:2009-11-06 12:43:23

标签: python sql mod-wsgi

我提出的每个解决方案都不是线程安全的。

def uuid(cls,db):
    u = hexlify(os.urandom(8)).decode('ascii')
    db.execute('SELECT sid FROM sessions WHERE sid=?',(u,))
    if db.fetch(): u=cls.uuid(db)
    else: db.execute('INSERT INTO sessions (sid) VALUES (?)',(u,))
    return u

9 个答案:

答案 0 :(得分:5)

import os, threading, Queue

def idmaker(aqueue):
  while True:
    u = hexlify(os.urandom(8)).decode('ascii')
    aqueue.put(u)

idqueue = Queue.Queue(2)

t = threading.Thread(target=idmaker, args=(idqueue,))
t.daemon = True
t.start()

def idgetter():
  return idqueue.get()

队列通常是在Python中同步线程的最佳方式 - 这在设计多线程系统时非常频繁,您首先想到的应该是“我怎样才能最好地使用队列”。基本思想是将一个线程专用于完全“拥有”共享资源或子系统,并让所有其他“工作”线程仅通过获取和/或放置该专用线程使用的队列来访问资源(队列本质上是线程安全的)

在这里,我们创建一个长度仅为2的idqueue(我们不希望id生成变得疯狂,事先制作了大量的id,这会浪费内存并耗尽熵池 - 而不是确定2是否是最佳的,但最佳位置肯定是一个非常小的整数;-),因此id生成器线程将在尝试添加第三个时阻塞,并等待直到某个空格打开队列。 idgetter(也可以通过顶级作业idgetter = idqueue.get简单定义)通常会在那里找到一个ID并等待(并为下一个作出空间!) - 如果没有,它本质上阻塞和等待,一旦id生成器在队列中放置了新的id,就会唤醒。

答案 1 :(得分:3)

您的算法没问题(就您的数据库API模块安全而言,线程安全)并且可能是最好的方法。它永远不会给你重复(假设你在sid上有PRIMARY或UNIQUE键),但你在INSERT上获得IntegrityError异常的机会很小。但是你的代码看起来不太好。最好使用一个尝试次数有限的循环而不是递归(如果代码中的某些错误可能变成无限的话):

for i in range(MAX_ATTEMPTS):
    sid = os.urandom(8).decode('hex')
    db.execute('SELECT COUNT(*) FROM sessions WHERE sid=?', (sid,))
    if not db.fetchone()[0]:
        # You can catch IntegrityError here and continue, but there are reasons
        # to avoid this.
        db.execute('INSERT INTO sessions (sid) VALUES (?)', (sid,))
        break
else:
    raise RuntimeError('Failed to generate unique session ID')

您可以提高读取的随机字符数,以使失败的机会更小。如果您希望缩短SID,base64.urlsafe_b64encode()是您的朋友,但是您必须确保您的数据库使用区分大小写的比较(MySQL的VARCHAR不适用,除非您为它设置二进制排序规则,但VARBINARY没关系。)

答案 2 :(得分:3)

我建议对Denis接受的答案进行一些小修改:

for i in range(MAX_ATTEMPTS):
    sid = os.urandom(8).decode('hex')
    try:
        db.execute('INSERT INTO sessions (sid) VALUES (?)', (sid,))
    except IntegrityError:
        continue
    break
else:
    raise RuntimeError('Failed to generate unique session ID')

我们只是尝试插入而不显式检查生成的ID。插入非常很少会失败,因此我们通常只需要进行一次数据库调用,而不是两次。

这将通过减少数据库调用来提高效率,而不会影响线程安全性(因为这将有效地由数据库引擎处理)。

答案 3 :(得分:2)

如果您需要线程安全,为什么不给随机数生成器一个使用共享锁的函数:

import threading
lock = threading.Lock()
def get_random_number(lock)
    with lock:
        print "This can only be done by one thread at a time"

如果调用get_random_number的所有线程使用相同的锁实例,那么其中只有一个可以创建一个随机数。

当然,您还可以使用此解决方案在您的应用程序中创建一个瓶颈。根据您的要求,还有其他解决方案,例如创建唯一标识符块然后并行使用它们。

答案 4 :(得分:1)

我不需要调用数据库:

>>> import uuid

# make a UUID based on the host ID and current time
>>> uuid.uuid1()
UUID('a8098c1a-f86e-11da-bd1a-00112444be1e')

来自this page

答案 5 :(得分:1)

我从一个线程唯一的ID开始,并且(不知何故)用线程本地计数器连接,然后通过加密哈希算法提供它。

答案 6 :(得分:0)

如果您绝对需要针对数据库验证uid并避免竞争条件,请使用事务:

BEGIN TRANSACTION
SELECT COUNT(*) FROM sessions WHERE sid=%s
INSERT INTO sessions (sid,...) VALUES (%s,...)
COMMIT

答案 7 :(得分:0)

每个帖子中都没有唯一的数据吗?我很难想象具有完全相同数据的两个线程。虽然我不打算这种可能性。

过去当我做过这种性质的事情时,线程通常会有一些独特之处。用户名或客户名称或此类性质的东西。我的解决方案是连接UserName,例如,以毫秒为单位的当前时间,然后散列该字符串并获取散列的十六进制摘要。这给了一个很好的字符串,总长度相同。

两个线程中的两个不同的John Smith(或其他)在同一毫秒内生成id的可能性非常小。如果这种可能性使人紧张,则可能需要如上所述的锁定路线。

正如已经提到的那样,已经有了获取GUID的例程。我个人喜欢摆弄哈希函数,所以我已经按照大型多线程系统中提到的方式成功完成了自己的工作。

最终由你来决定你是否确实拥有重复数据的线程。一定要选择一个好的哈希算法。我已经成功使用了md5,但我已经读过可以使用md5生成哈希冲突,尽管我从未这样做过。最近我一直在使用sha1。

答案 8 :(得分:0)

mkdtemp应该是线程安全的,简单而安全的:

def uuid():
    import tempfile,os
    _tdir = tempfile.mkdtemp(prefix='uuid_')
    _uuid = os.path.basename(_tdir)
    os.rmdir(_tdir)
    return _uuid