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

2024-06-15 13:16:17 发布

您现在位置:Python中文网/ 问答频道 /正文

我想出的每一个解决方案都不是省力的。在

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

Tags: executedbuuidosdefascii解决方案select
3条回答

你的算法是好的(线程安全,只要你的dbapi模块是安全的),可能是最好的方法。它不会给您重复的(假设您在sid上有主键或唯一键),但是您在插入时获得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')

你可以增加随机字符的数目,用来使失败的机会更小。base64.urlsafe_b64encode()是您的朋友,如果您想缩短SID,但是您必须确保您的数据库对此列使用区分大小写的比较(MySQL的VARCHAR不适合,除非您为其设置二进制排序规则,但是VARBINARY是可以的)。在

我建议对丹尼斯接受的答案稍作修改:

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。insert几乎不会失败,因此我们通常只需要进行一次数据库调用,而不是两次。在

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

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中同步线程的最佳方式——这一点非常频繁,以至于在设计多线程系统时,您的第一个想法应该是“如何最好地使用队列来实现这一点”。其基本思想是让一个线程完全“拥有”一个共享资源或子系统,并让所有其他“工作”线程仅通过该专用线程使用的get和/或put访问该资源(队列本质上是线程安全的)。在

在这里,我们生成一个长度只有2的idqueue(我们不想让id生成变得疯狂,预先生成大量的id,这会浪费内存并耗尽熵池——不确定2是否是最佳的,但最佳位置肯定是一个非常小的整数;-),因此在尝试添加第三个,等到队列中有空位。idgetter(也可以简单地通过顶级赋值来定义,idgetter = idqueue.get)通常会找到一个已经在那里等待的id(并为下一个id腾出空间!)--如果没有,它本质上会阻塞并等待,一旦id生成器在队列中放置了一个新的id,就会立即唤醒。在

相关问题 更多 >