Saturday, March 29, 2008

什么时候mutex不够,还需要condition variable?

什么时候mutex不够,还需要condition variable?
假设有共享的资源sum,与之相关联的mutex 是lock_s.假设每个线程对sum的操作很简单的,与sum的状态无关,比如只是sum++.那么只用mutex足够了.程序员只要确保每个线程操作前,取得lock,然后sum++,再unlock即可.

每个线程的代码将像这样
add()
{
pthread_mutex_lock(lock_s);
sum++;
pthread_mutex_unlock(lock_s);
}

如果操作比较复杂,假设线程t0,t1,t2的操作是sum++,而线程t3则是在sum到达100的时候,打印出一条信息,并对sum清零.这种情况下,如果只用mutex, 则t3需要一个循环,每个循环里先取得lock_s,然后检查sum的状态,如果sum>=100,则打印并清零,然后unlock.如果sum<100,则unlock,并sleep()本线程合适的一段时间.

这个时候,t0,t1,t2的代码不变,t3的代码如下
print()
{
while (1)
{
pthread_mutex_lock(lock_s);
if(sum<100)
{
printf(“sum reach 100!”);
pthread_mutex_unlock(lock_s);
}
else
{
pthread_mutex_unlock(lock_s);
my_thread_sleep(100);
return OK;
}
}
}

这种办法有两个问题
1) sum在大多数情况下不会到达100,那么对t3的代码来说,大多数情况下,走的是else分支,只是lock和unlock,然后sleep().这浪费了CPU处理时间.
2) 为了节省CPU处理时间,t3会在探测到sum没到达100的时候sleep()一段时间.这样却又带来另外一个问题,亦即t3响应速度下降.可能在sum到达200的时候,t4才会醒过来.
3) 这样,程序员在设置sleep()时间的时候陷入两难境地,设置得太短了节省不了资源,太长了又降低响应速度.真是难办啊!

这个时候,condition variable内裤外穿,从天而降,拯救了焦头烂额的你.

你首先定义一个condition variable.
pthread_cond_t cond_sum_ready=PTHREAD_COND_INITIALIZER;

t0,t1,t2的代码只要后面加两行,像这样
add()
{
pthread_mutex_lock(lock_s);
sum++;
pthread_mutex_unlock(lock_s);
if(sum>=100)
pthread_cond_signal(&cond_sum_ready);
}
而t3的代码则是
print
{
pthread_mutex_lock(lock_s);
while(sum<100)
pthread_cond_wait(&cond_sum_ready, &lock_s);
printf(“sum is over 100!”);
sum=0;
pthread_mutex_unlock(lock_s);
return OK;
}

注意两点:
1) 在thread_cond_wait()之前,必须先lock相关联的mutex, 因为假如目标条件未满足,pthread_cond_wait()实际上会unlock该mutex, 然后block,在目标条件满足后再重新lock该mutex, 然后返回.
2) 为什么是while(sum<100),而不是if(sum<100) ?这是因为在pthread_cond_signal()和pthread_cond_wait()返回之间,有时间差,假设在这个时间差内,还有另外一个线程t4又把sum减少到100以下了,那么t3在pthread_cond_wait()返回之后,显然应该再检查一遍sum的大小.这就是用while的用意.

Labels:

POSIX thread学习

POSIX thread学习
1. 问题1, restrict这个关键词干嘛用的?

2. pthread_create()相当于fork().用于产生新线程.一般而言,attr会被设置为NULL.
原型int pthread_create(pthread_t * restrict tidp,
const pthread_attr_t * attr,
void * (*start_rtn)(void *),
void * restrict arg
);

3. pthread_cancel() 相当于kill(),(APUE 2ed.中说相当于abort())用于取消另外一个线程。
原型 int pthread_cancel(pthread_t pid);

3. pthread_exit() 相当于exit(),用于本线程退出.
原型void * pthread_exit(void *rval_ptr);

4. pthread_join()相当于waitpid(),用于取得另外一个线程的退出状态,亦即exit state. pthread_detach()过的线程则不可以再被pthread_join()了.
原型 int pthread_join(pthread_t tid, void **rval_ptr);
Int pthread_detach(pthread_t tid);

5. pthread_cleanup_push()则对应于atexit(),用于注册线程退出时应该调用的cleanup函数.与之对应的是pthread_cleanup_pop().atexit()则无此对应物.
原型 void pthread_cleanup_push(void (cleanup_fcn)(void *), void * arg);
Void pthread_cleanup_pop(int execute);

6. pthread_self()相当于getpid(),它返回调用线程的pthread identifier.

下面是线程同步的笔记.

7. Pthread mutex 名字源于mutual exclusive.
pthread_mutex_t可以用pthread_mutex_init()初始化,也可以用PTHREAD_MUTEX_INITIALIZER初始化.
pthread_mutes_t使用后须用pthread_mutex_destroy()清除.但是注意,pthread_destroy()并不释放内存.比如
pthread_mutex_t *lockp=malloc(sizeof(pthread_mutex_t));
pthread_mutex_init(lockp, NULL);
//此处使用…
pthread_mutex_destroy(lockp);
free(lockp);
最后一行的free是不能被倒数第二行的destroy替代的.

问题:destroy究竟释放些什么资源?有时间研究下其实现.

8. pthread_mutex_lock()与pthread_mutex_trylock()的区别在,后者不会block,假如锁已经被其他线程取得,则后者会返回EBUSY. pthread_mutex_unlock()用于释放锁,

9. 还有Reader_Writer Lock,比较烦琐,详见APUE 2ed P379.

10. 关于condition variable,参见下一篇帖子

Labels:

读书笔记之内存分配

Linux Programming by Example----内存分配


1. 作者说Never assume that memory allocation will succeed.因此,在每次分配之后,都要检查内存分配函数的返回值。如果是NULL,则分配失败。

2. 把一个指针free()两次,称作double free,其后果是ISO C undefined,亦即是说,其后果由implement决定。那么究竟会有什么危险?因为只要implement实现得比较周全,double free可以完全无害。

3. malloc(), calloc() & realloc().三个函数的关系如下:
malloc()分配指定大小的内存,calloc()不仅分配内存,还把内存清零。所以把malloc() wrap一下,即可实现calloc()。
realloc()原型是void* realloc(void* ptr, size_t newsize), 在ptr为NULL的时候,它可以实现malloc(),而在newsize为0的时候,它相当于free()。

4. realloc()函数有一个非常需要注意的特点,它可能移动整块ptr所指的内存,所以realloc之后,所有之前的指针都失效了,需要重新赋值。这一点,在ptr是数组头指针的时候,尤其重要。比如
char ptr[100];
char *second=ptr[1];
realloc(ptr,200);
之后,second失效。不能再用second存取第二个char了。

5. alloca()函数比较怪异,它不是在heap,而是在stack分配内存,因此它分配的内存无需程序员free(),调用函数退出后内存自然释放了。这个函数不是标准函数,ISO C或者POSIX都没有包含它。不提倡使用。不过我觉得这个函数看上去很handy。

6. 问题, malloc为什么叫malloc,calloc为什么叫calloc,realloc我倒是明白。

7. char* strdup(const char* str)这个函数非常怪异,它的实现调用malloc(),返回一个指向堆内存的指针。因此它返回的指针在使用完毕后,应该用free()释放。我坚决不会使用这个愚蠢的函数,尽管它是POSIX标准包含的。

8. gawk使用自己的内存allocator,用宏实现,部分代码如下:
#define getnode(n) if(nextfree) n=nextfree,nextfree=nextfree->nextp;\
else n=more_nodes
#define freenode(n) ((n)->flag=0, (n)->exec_count=0,\
(n)->nextp=nextfree,nextfree=(n))
这个allocator在需要大量同样规则的小内存块的情况下,效率较高。

Labels: