3

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

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
4

9 回答 9

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 中同步线程的最佳方式——这种情况足够频繁,以至于在设计多线程系统时,您的第一个想法应该是“我怎样才能最好地使用队列来做到这一点”。基本思想是让一个线程完全“拥有”共享资源或子系统,并让所有其他“工作”线程仅通过获取和/或放入该专用线程使用的队列来访问资源(队列本质上是线程安全的) .

在这里,我们制作idqueue一个长度仅为 2 的 (我们不希望 id 生成变得疯狂,预先制作很多 id,这会浪费内存并耗尽熵池——不确定是否2是最优的,但甜蜜spot 肯定会是一个非常小的整数;-),因此 id 生成器线程将在尝试添加第三个时阻塞,并等待队列中打开一些空间。 idgetter(也可以简单地由顶级赋值定义,idgetter = idqueue.get)通常会找到一个已经存在的 id 并等待(并为下一个腾出空间!)——如果没有,它本质上会阻塞并等待,一旦唤醒id 生成器在队列中放置了一个新的 id。

于 2009-11-06T15:50:08.773 回答
3

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

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 是可以的)。

于 2009-11-06T13:53:49.523 回答
3

我建议对丹尼斯接受的答案做一个小的修改:

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。插入很少会失败,所以我们通常只需要进行一次数据库调用,而不是两次。

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

于 2009-11-07T10:08:51.940 回答
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使用同一个锁实例,那么每次只有一个线程可以创建一个随机数。

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

于 2009-11-06T12:56:38.993 回答
1

无需调用我认为的数据库:

>>> import uuid

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

这个页面

于 2009-11-06T12:47:07.580 回答
1

我将从线程唯一 ID 开始,并(以某种方式)将其与线程本地计数器连接起来,然后通过加密哈希算法提供给它。

于 2009-11-06T12:51:36.340 回答
0

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

BEGIN TRANSACTION
SELECT COUNT(*) FROM sessions WHERE sid=%s
INSERT INTO sessions (sid,...) VALUES (%s,...)
COMMIT
于 2009-11-06T12:50:51.627 回答
0

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

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

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

正如已经提到的,已经有获取 GUID 的例程。我个人喜欢摆弄散列函数,所以我以在大型多线程系统上提到的方式成功地推出了自己的方法。

最终由您决定是否真的有重复数据的线程。一定要选择一个好的散列算法。我已经成功地使用了 md5,但是我读到它可能会与 md5 产生哈希冲突,尽管我从来没有这样做过。最近我一直在使用sha1。

于 2009-11-06T17:11:53.850 回答
0

mkdtemp 应该是线程安全的、简单且安全的:

def uuid():
    import tempfile,os
    _tdir = tempfile.mkdtemp(prefix='uuid_')
    _uuid = os.path.basename(_tdir)
    os.rmdir(_tdir)
    return _uuid
于 2009-11-07T21:45:57.980 回答