需求:
key生成器功能是生成一个唯一的key,不能重复。序列化是将已经生成的key序列化到磁盘,以防机器宕机,key丢失导致生成重复的key;我们的需求key是顺序增长的。
方案一:
先生成key后持久化;用户模块调用key生成器时,生成器先将key返回给用户,并将返回给用户的最大key存储在内存中,到一定量后(比如,已经返回给用户64个key)将key序列化到文件。文件中还存在一个标识位,正常打开、关闭文件时设置相应的标识位。key生成器启动读取序列化文件时,判断文件是否被正常关闭,是则正常读取文件中最大的key,否则读取文件中的key加上64(略过,保证key不重复),再继续去生成key。
方案二:
先持久化后生成key ;用户模块调用key生成器时,先将key序列化到文件,再将key返回给用户。直接序列化一个区间到文件,如程序启动时,key从1开始,不直接序列化1,而将64序列化到文件,这样之后从2-63的调用就不需再去序列化文件了,减少了文件的写操作。此方案不需在文件中存储任何标识位,机器宕机之后,序列化文件中的key肯定是最大的,后续的key不会重复。
以上两种方案,都是从别人的代码和交流中获得的,我比较喜欢第二种方案,简单,省去了不必要的错误恢复。有时候换一种思路或者说逆向思路可以让你的设计更为简单、容易理解。
2013年1月14日星期一
2013年1月11日星期五
多线程条件变量和互斥锁的使用
多线程同步,是多线程编程中肯定会遇到的问题。常见的有pthread_mutex_t、pthread_rwlock_t和pthread_cond_t,前面两个是锁,后面叫条件变量(条件成立时,通知通过阻塞线程的机制;其中条件的修改是需要锁来保护的)。
条件变量需要和互斥锁以及一个条件结合使用,条件表示某一种情况的达成,比如队列中有元素,可以去取元素了,互斥锁用来使多线程对条件的修改原子化,条件变量用来做通知,向队列放元素的线程通知取元素线程你们可以来取元素了。
这里有一个令人头疼的问题,就是pthread_cond_signal()和pthread_cond_broadcast()函数是在锁内调用还是在锁外调用,如下:
1、
pthread_mutex_lock(&lock);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&lock);
2、
pthread_mutex_lock(&lock);
pthread_mutex_unlock(&lock);
pthread_cond_signal(&cond);
这个问题,考虑是否浪费了CPU,考虑任务的调度机制,令人头疼;最后在starckoverflow看到一种解释,我觉得很好:不要考虑以上那些不确定的因素,考虑代码正确性就可以,修改条件和通知其它线程条件被修改应该是在一起的,所以放到锁内更好理解。
另外一点需要注意的是,条件判断使用while循环。
pthread_mutex_lock(&lock);
while(cond == NULL) {
pthread_cond_wait(&cond, &lock);
}
pthread_mutex_unlock(&lock);
条件变量需要和互斥锁以及一个条件结合使用,条件表示某一种情况的达成,比如队列中有元素,可以去取元素了,互斥锁用来使多线程对条件的修改原子化,条件变量用来做通知,向队列放元素的线程通知取元素线程你们可以来取元素了。
这里有一个令人头疼的问题,就是pthread_cond_signal()和pthread_cond_broadcast()函数是在锁内调用还是在锁外调用,如下:
1、
pthread_mutex_lock(&lock);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&lock);
2、
pthread_mutex_lock(&lock);
pthread_mutex_unlock(&lock);
pthread_cond_signal(&cond);
这个问题,考虑是否浪费了CPU,考虑任务的调度机制,令人头疼;最后在starckoverflow看到一种解释,我觉得很好:不要考虑以上那些不确定的因素,考虑代码正确性就可以,修改条件和通知其它线程条件被修改应该是在一起的,所以放到锁内更好理解。
另外一点需要注意的是,条件判断使用while循环。
pthread_mutex_lock(&lock);
while(cond == NULL) {
pthread_cond_wait(&cond, &lock);
}
pthread_mutex_unlock(&lock);
2013年1月10日星期四
堆栈实现先进先出队列
实现思路:
使用两个大小相同的堆栈(命名为s1,s2),来实现堆栈。队列包含一下几个操作:创建、注销、满判断、空判断、入队、出队。
创建:
创建管理结构体,结构体包含队列容量、使用量、包含两个栈指针的数组。使用另外的库函数来创建堆栈。两个栈的大小和队列的容量相同。
注销:
顺序注销两个栈,以及管理结构体。
满判断:
空判断:
根据管理结构体中存储的容量和使用量来判断。
入队:
首先使用前面的函数判断队列是否满,满则出错。将数据压入栈s1,并增加使用量计数。
出队:
首先判断队列是否为空,空则出错。判断s2栈是否为空,空则将s1栈内容出栈,入栈到s2,s1的最后一个元素直接返回给函数调用者,不入栈;s2栈非空,则直接从s2中返回一个元素给函数调用者。返回元素要较少使用量计数。
代码实现见github(https://github.com/zhangst/exercise/tree/master/interview/fifo),这种实现还有一个问题,空间浪费。创建一个容量为16的队列,需要两个容量为16的堆栈才能实现,总空间占用为32了。
使用两个大小相同的堆栈(命名为s1,s2),来实现堆栈。队列包含一下几个操作:创建、注销、满判断、空判断、入队、出队。
创建:
创建管理结构体,结构体包含队列容量、使用量、包含两个栈指针的数组。使用另外的库函数来创建堆栈。两个栈的大小和队列的容量相同。
注销:
顺序注销两个栈,以及管理结构体。
满判断:
空判断:
根据管理结构体中存储的容量和使用量来判断。
入队:
首先使用前面的函数判断队列是否满,满则出错。将数据压入栈s1,并增加使用量计数。
出队:
首先判断队列是否为空,空则出错。判断s2栈是否为空,空则将s1栈内容出栈,入栈到s2,s1的最后一个元素直接返回给函数调用者,不入栈;s2栈非空,则直接从s2中返回一个元素给函数调用者。返回元素要较少使用量计数。
代码实现见github(https://github.com/zhangst/exercise/tree/master/interview/fifo),这种实现还有一个问题,空间浪费。创建一个容量为16的队列,需要两个容量为16的堆栈才能实现,总空间占用为32了。
订阅:
博文 (Atom)