系统程序员成长计划-并发(一)(下)
2,475 views| 2008-12-15| 李先静| 系统程序员成长计划| | 7 条评论转载时请注明出处和作者联系方式
文章出处:http://www.limodev.cn/blog
作者联系方式:李先静 <xianjimli@gmail.com>
Linux下的多线程编程使用pthread(POSIX Thread)函数库,使用时包含头文件pthread.h,链接共享库libpthread.so。这里顺便说一下gcc链接共享库的方式:-L用来指定共享库所在目录,系统库目录不用指定。-l用来指定要链接的共享库,只需要指定库的名字就行了,如:-lpthread,而不是-llibpthread.so。看起来有点怪,这样做的原因是共享库通常带有版本号,指定全文件名就意味着你要绑定到特定版本的共享库上,只指定名字则在可以运行时通过环境变量来选择要使用的共享库,这样能够给软件升级带来的方便。
pthread函数库的使用相对比较简单,读者可以在终端下运行man pthread_create阅读相关函数的手册,也可以到网上找些例子参考。具体使用方法我们就不讲了,这里介绍一下初学者常犯的错误:
o 用临时变量作为线程参数的问题。
#include <stdio.h>
#include <pthread.h>
#include <assert.h>
void* start_routine(void* param)
{
char* str = (char*)param;
printf("%s:%s\n", __func__, str);
return NULL;
}
pthread_t create_test_thread()
{
pthread_t id = 0;
char str[] = "it is ok!";
pthread_create(&id, NULL, start_routine, str);
return id;
}
int main(int argc, char* argv[])
{
void* ret = NULL;
pthread_t id = create_test_thread();
pthread_join(id, &ret);
return 0;
}
分析:由于新线程和当前线程是并发的,谁先谁后是无法预测的。可能create_test_thread 已经执行完了,str已经被释放了,新线程才拿到这参数,此时它的内容已经无法确定了,打印出的字符串自然是随机的。
o 线程参数共享的问题。
#include <stdio.h>
#include <pthread.h>
#include <assert.h>
void* start_routine(void* param)
{
int index = *(int*)param;
printf("%s:%d\n", __func__, index);
return NULL;
}
#define THREADS_NR 10
void create_test_threads()
{
int i = 0;
void* ret = NULL;
pthread_t ids[THREADS_NR] = {0};
for(i = 0; i < THREADS_NR; i++)
{
pthread_create(ids + i, NULL, start_routine, &i);
}
for(i = 0; i < THREADS_NR; i++)
{
pthread_join(ids[i], &ret);
}
return ;
}
int main(int argc, char* argv[])
{
create_test_threads();
return 0;
}
分析:由于新线程和当前线程是并发的,谁先谁后是无法预测的。i在不断变化,所以新线程拿到的参数值是无法预知的,打印出的字符串自然也是随机的。
o 虚假并发。
#include <stdio.h>
#include <pthread.h>
#include <assert.h>
void* start_routine(void* param)
{
int index = *(int*)param;
printf("%s:%d\n", __func__, index);
return NULL;
}
#define THREADS_NR 10
void create_test_threads()
{
int i = 0;
void* ret = NULL;
pthread_t ids[THREADS_NR] = {0};
for(i = 0; i < THREADS_NR; i++)
{
pthread_create(ids + i, NULL, start_routine, &i);
pthread_join(ids[i], &ret);
}
return ;
}
int main(int argc, char* argv[])
{
create_test_threads();
return 0;
}
分析:因为pthread_join会阻塞直到线程退出,所以这些线程实际上是串行执行的,一个退出了,才创建下一个。当年一个同事写了一个多线程的测试程序,就是这样写的,结果没有测试出一个潜伏的问题,直到产品运行时,这个问题才暴露出来。
访问线程共享的数据时要加锁,让访问串行化,否则就会出问题。比如,可能你正在访问的双向链表的某个结点时,它已经被另外一个线程删掉了。加锁的方式有很多种,像互斥锁(mutex= mutual exclusive lock),信号量(semaphore)和自旋锁(spin lock)等都是常用的,它们的使用同样很简单,我们就不多说了。
在加锁/解锁时,初学者常犯两个错误:
o 存在遗漏解锁的路径。初学者常见的做法就是,进入某个临界函数时加锁,在函数结尾的地方解锁,我甚至见过这种写法:
{
/*这里加锁*/
…
return …;
/*这里解锁*/
}
如果你也犯了这种错误,应该好好反思一下。有时候,return的地方太多,在某一处忘记解锁是可能的,就像内存泄露一样,只是忘记解锁的后果更严重。像下面这个例子:
Ret dlist_insert(DList* thiz, size_t index, void* data)
{
DListNode* node = NULL;
DListNode* cursor = NULL;
return_val_if_fail(thiz != NULL, RET_INVALID_PARAMS);
dlist_lock(thiz);
if((node = dlist_create_node(thiz, data)) == NULL)
{
dlist_unlock(thiz);
return RET_OOM;
}
if(thiz->first == NULL)
{
thiz->first = node;
dlist_unlock(thiz);
return RET_OK;
}
...
dlist_unlock(thiz);
return RET_OK;
}
如果一个函数有五六个甚至更多的地方返回,遗忘一两个地方是很常见的,即使没有忘记,每个返回的地方都要去解锁和释放相关资源也是很麻烦的。在这种情况下,我们最好是实现单入口单出的函数,常见的做法有两种:
一种是使用goto语句(在linux内核里大量使用)。示例如下:
Ret dlist_insert(DList* thiz, size_t index, void* data)
{
Ret ret = RET_OK;
DListNode* node = NULL;
DListNode* cursor = NULL;
return_val_if_fail(thiz != NULL, RET_INVALID_PARAMS);
dlist_lock(thiz);
if((node = dlist_create_node(thiz, data)) == NULL)
{
ret = RET_OOM;
goto done;
}
if(thiz->first == NULL)
{
thiz->first = node;
goto done;
}
...
done:
dlist_unlock(thiz);
return ret;
}
另外一种是使用do{}while(0);语句,出于受教科书的影响(不要用goto语句),我习惯了这种做法。示例如下:
Ret dlist_insert(DList* thiz, size_t index, void* data)
{
Ret ret = RET_OK;
DListNode* node = NULL;
DListNode* cursor = NULL;
return_val_if_fail(thiz != NULL, RET_INVALID_PARAMS);
dlist_lock(thiz);
do
{
if((node = dlist_create_node(thiz, data)) == NULL)
{
ret = RET_OOM;
break;
}
if(thiz->first == NULL)
{
thiz->first = node;
break;
}
...
}while(0);
dlist_unlock(thiz);
return ret;
}
o 加锁顺序的问题。有时候为了提高效率,常常降低加锁的粒度,访问时不是用一个锁锁住整个数据结构,而是用多个锁来控制数据结构各个部分。这样一个线程访问数据结构的这部分时,另外一个线程还可以访问数据结构的其它部分。但是在有的情况下,你需要同时锁几个锁,这时就要注意了:所有线程一定要按相同的顺序加锁,相反的顺序解锁。否则就可能出现死锁,两个线程都拿到对方需要的锁,结果出现互相等待的情况。
系统程序员成长计划 Share
Comments
Tags
Recent Posts
Most Viewed
- 系统程序员成长计划写作提纲 - 19,605 views
- Android IPC机制详解 - 6,277 views
- 系统程序员成长计划-走近专业程序员(上) - 6,253 views
- 系统程序员成长计划-写得又快又好的秘诀(一) - 5,391 views
- 系统程序员成长计划-背景知识 - 5,070 views
- i++循环与i–循环的执行效率 - 4,712 views
- 系统程序员成长计划-Write once, run anywhere(WORA)(上) - 4,700 views
- 系统程序员成长计划-走近专业程序员(下) - 4,254 views
- Linux下的调试工具 - 4,017 views
- Advanced Linux Sound Architecture (ALSA) 研究笔记 - 4,017 views
- 系统程序员成长计划-序 - 3,985 views
- 系统程序员成长计划-写得又快又好的秘诀(三) - 3,930 views
- 中国人与自由软件文化研究(搞笑版) - 3,735 views
- Android中的MessageQueue,Handler,Looper和Thread - 3,686 views
- 答复:我不会OOO,仍然可以XXX - 3,658 views
Categories
- Android (28)
- Broncho-A1-Hack (6)
- DirectFB (7)
- FTK(嵌入式GUI) (24)
- GTK+ (29)
- KVM hack notes (8)
- Linux Mobile (65)
- Management (5)
- Mozilla (9)
- Open Source (5)
- Programming (34)
- Tools (9)
- Uncategorized (23)
- Win32 (3)
- X Windows (31)
- 沉思录 (29)
- 系统程序员成长计划 (67)
Blogroll
gallery
Linux guru
推荐网站
Recent Comments
- Dig on 嵌入式GUI FTK设计与实现-事件源(FtkSource)
- 用心生活每一天 » GNU gprof: linux profiling tools 使用 on gcc profiling的工作原理
- JavaScript for: i++ vs i–-传播、沟通、分享-一直“有你” on i++循环与i–循环的执行效率
- Frankly Law on 嵌入式GUI FTK介绍(11)-交叉编译
- tracing on Linux下的调试工具
- ndljsn on FTK移植指南(初稿)
- tracing on 爬塘朗山
- tracing on GTK+(基于DirectFB)的字体处理
- Kely on 系统程序员成长计划写作提纲
- tracing on 爬塘朗山



December 15th, 2008
Dig
December 16th, 2008
正在看一段满是锁的代码,头晕啊
meierduo
December 22nd, 2008
void (*fp_read)(DuLinkList *,int );//带参数的函数指针
void reader_function (DuLinkList L,int i);
main()
{……
fp_read=&reader_function;
pthread_create(&reader, NULL, (*fp_read)(List1,4), NULL);
……
}
编译的时候 error: invalid use of void expression
请问这个带参数的reader_function 该怎么用啊?
meierduo
December 22nd, 2008
sorry ,知道错哪了,pthread_create不能这样用
admin
December 22nd, 2008
The linux mobile development » Blog Archive » 系统程序员成长计划写作提纲
May 25th, 2009
[...] 3.2 排序算法 3.3 有序数组的应用 第4章 并发与同步 完成 4.1 多线程编程(上)(下) 4.2 同步(上)(下) 4.3 嵌套锁(上)(下) 4.4 读写锁(上)(下) 4.5 [...]
noCom
June 24th, 2009
do
{
…
…
}
}while(0);
//公共操作
…
return ret;
///////////////////////////////////////////////////
这方法好,呵,受教很多啊
unknown
July 17th, 2009
一般来说,向下goto不会影响程序的可读性,goto用得好有时会让程序更易读。
想象一个从多重循环中不断break出来的程序,用goto就简单明了多了。