分类 技术志 下的文章

同步问题讨论-Tony与Alex的对话系列

Alex正在电脑前面作冥思苦想状,这时Tony悄悄地走到Alex的身后,观察了一会儿…

Tony : 看来今天我们要讨论同步问题了。
Alex : (惊奇地回头)。Hey Man , you scared me! 你说的没错,我正在学习同步这一块儿呢,有什么高见不妨说出来吧,我洗耳恭听!
Tony : 不敢不敢。关于进程和线程同步的问题,W. Richard Stevens在他的那本经典的“UNIX Network Programming Volume 2”中有过详尽的讲解,你不妨仔细阅读一下。
Alex : 远水解不了近渴。你还是大概跟我说说吧!
Tony : OK, 我们就拿一个最简单例子来探讨一下吧。在拿出例子之前我们来回顾一下同步的由来。Alex你说说为什么要同步呢?
Alex : 有共享就要同步,就好比超市的POS,如果没有好的同步顾客活动的策略,那超市不就乱了套了么,大家都争着抢着去结账。
Tony : 嗯,没错。mess world is not what we need! 互斥和条件变量是我们经常使用的同步手段,当然更高级的还有信号灯等。
Alex : 逐一说明吧,看来今天又会有不小的收获^_^
Tony : 历史上有个特别有名的问题叫做“生产者-消费者”问题,又叫“有限缓冲区”问题,我们今天的例子大约就是这个样子的。
Alex : (入迷的样子)
Tony : 我们的例子是这样的,我们有“生产者”和“消费者”两个角色,他们共享某一整型变量,规定如下:
       1)生产者发现产品已经被消费了,便生产,即将该共享变量置为1;
       2)消费者发现有产品了,便消费,即将该共享变量置为0;
       很简单吧。我们还是用老办法,由简入难,我们可以使用最简单的手段“互斥锁”来完成这个任务。
Alex : 我知道“互斥锁”,但是了解得并不深,先讲讲理论把!
Tony : 互斥,顾名思义互相排斥,它是最基本的同步手段,一般用来保护“临界区”,“临界区”是一段代码,看起来互斥保护了临界区这段代码的,实质上互斥保护的是“临界区”中被操纵的数据。
Alex : 互斥是不是即可用于线程,也可以用于进程呢?
Tony : 都可以,在我们的例子中我们使用线程,因为线程间共享一个数据空间,实现起来比较容易;进程间要想共享数据就需要额外的支持,比如共享内存等。
Alex : 噢。
Tony : 我们开始吧,按照例子中所述我们应该有两个线程,分别代表生产者和消费者。按照W. Richard Stevens的指导,我们将我们的互斥锁和我们的共享数据放在一个结构体内。

//数据结构定义
#define MAX_COUNT 100

typedef struct sharedata_t{
 pthread_mutex_t lock;
 int val;
}sharedata_t;

sharedata_t shared = {PTHREAD_MUTEX_INITIALIZER};

//主函数
int main(){
 pthread_t producer;
 pthread_t consumer;

 pthread_create(&producer, NULL, produce, NULL);
 pthread_create(&consumer, NULL, consume, NULL);

 pthread_join(producer, NULL);
 pthread_join(consumer, NULL);

 return 0;
}
这些都很简单,关键的是produce和consume两个线程执行函数。
Alex : 如前面所说,produce和consume在访问shared时候一定要先对lock上锁。
Tony : 没错,在任意时刻都只有一个线程在操纵shared变量。代码如下:

void *produce(void *arg){
 int count = 0;
 for( ; ; ){
  pthread_mutex_lock(&shared.lock);
  if(shared.val == 1){//如果已经生产了我就不生产了,直到消费者消费掉
   pthread_mutex_unlock(&shared.lock);
   continue;
  }
  shared.val = 1;
  count++;
  if(count > MAX_COUNT){
   pthread_mutex_unlock(&shared.lock);
   break;
  }
  printf("the %d th produce\n", count);//线程共享进程stdout缓冲区,如果不加以保护,就会被另一个线程的输出刷新
  pthread_mutex_unlock(&shared.lock);
 }
}

void *consume(void *arg){
 int count = 0;
 for( ; ; ){
  pthread_mutex_lock(&shared.lock);
  if(shared.val == 0){//如果还没生产呢,我就暂时不能消费
   pthread_mutex_unlock(&shared.lock);
   continue;
  }
  shared.val = 0;
  count++;
  if(count > MAX_COUNT){
   pthread_mutex_unlock(&shared.lock);
   break;
  }
  printf("the %d th consume\n", count);
  pthread_mutex_unlock(&shared.lock);
 }
}

Tony : 现在这个程序是正确的,但是却不是理想的,他的输出结果肯定是如下的:
the 1 th produce
the 1 th consume
the 2 th produce
the 2 th consume

the 100 th produce
the 100 th consume

Alex : 是producer和consumer交替对吧。
Tony : 没错!运行一下,你感觉如何呢?
Alex : 好像有些慢!我觉得produce和consume两个函数中关于shared.val的值的轮转测试是比较耗时的,而且每次测试前后都要上锁、解锁。
Tony : 说的没错!像这样的轮询是极其浪费CPU时间的。我们不是没有办法解决的,我们可以利用条件变量的方式来解决它,不过针对这个例子来说,使用条件变量从代码上看理解起来就会有些困难了。
Alex : 继续说!
Tony : 条件变量提供一种等待-唤醒机制,可以这样理解如果消费者发现没有产品,它并不继续轮训,而是睡眠,直到生产者生产出产品,并将之唤醒消费。反过来说也一样,那就是如果生产者发现生产出来的产品还没有被消费者消费掉,就同样睡眠,直到消费者将产品消费掉,并将生产者唤醒生产,这样就省下了大量的CPU时间,性能提升可不是一点半点的。不过我们实现起来的时候要更加小心。
Alex : Just go on!
Tony : 要想使用条件变量,我们还需要定一个结构,用来存放我们的条件。

typedef struct conddata_t{
 pthread_mutex_t lock;
 pthread_cond_t cond;
 int ready;
}conddata_t;

conddata_t condd = {PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, 0};

主函数无需改变,修改后的produce和consume如下,由于不好理解,所以我讲上了不少的注释,可以帮助理解。

void *produce(void *arg){
 int count = 0;

 for( ; ; ){
  pthread_mutex_lock(&condd.lock);
  while(condd.ready == 1)//已生产,还没消费完,此时producer休眠
   pthread_cond_wait(&condd.cond, &condd.lock);
  condd.ready = 0;//消费完,还没生产呢,此时consumer休眠
  pthread_mutex_unlock(&condd.lock);

  pthread_mutex_lock(&shared.lock);
  shared.val = 1;
  count++;
  if(count >= MAX_COUNT){
   pthread_mutex_unlock(&shared.lock);
   condd.ready = 1;//生产者生产完最后一个后退出了,告诉消费者
   pthread_cond_signal(&condd.cond);//告诉消费者可以消费最后一个了
   break;
  }
  printf("the %d th produce\n", count);//线程共享进程stdout缓冲区,如果不加以保护,就会被另一个线程的输出刷新
  pthread_mutex_unlock(&shared.lock);

  pthread_mutex_lock(&condd.lock);
  condd.ready = 1;//生产完了,等待消费
  pthread_mutex_unlock(&condd.lock);
  pthread_cond_signal(&condd.cond);//告诉消费者可以消费了
 }
}

void *consume(void *arg){
 int count = 0;
 for( ; ; ){
  pthread_mutex_lock(&condd.lock);
  while(condd.ready == 0)//没有东西可以消费,消费者休眠
   pthread_cond_wait(&condd.cond, &condd.lock);
  condd.ready = 1;//这在消费,请生产者等待,生产者休眠
  pthread_mutex_unlock(&condd.lock);

  pthread_mutex_lock(&shared.lock);
  shared.val = 0;
  count++;
  if(count >= MAX_COUNT){
   pthread_mutex_unlock(&shared.lock);
   break;
  }
  printf("the %d th consume\n", count);
  pthread_mutex_unlock(&shared.lock);

  pthread_mutex_lock(&condd.lock);
  condd.ready = 0;//告诉生产者消费完了,该生产了
  pthread_mutex_unlock(&condd.lock);
  pthread_cond_signal(&condd.cond);
 }

}

代码更长了。有些东西不必要解释,看看注释认真思考一下就能得到答案的。

Alex : 晓得。
Tony : 这回我们来看看性能,第一个实现cpu占用98% 耗时近10秒,而第二个实现几乎瞬间完成。
Alex : 我还在思考,的确不容易理解。
Tony : 还要注意的是资源的释放,这可是一个重要的问题。你慢慢思考吧,我去喝杯coffee。^_^

从技术到管理的对话-Tony与Alex的对话系列

5月末我参加了一次“从技术到管理的”培训,总体来说还是有所收获的。这段时间我一直想把自己的收获总结出来与大家分享,但是也一直没找到一个很好的形式来表达,我想简单的罗列一些规则和技巧是最最乏味的。在我的“关于Tony与Alex的对话系列的一点说明”一文中曾经将“Tony与Alex对话系列”定位为技术类的系列文章,但是经过这几天的思考,发现它同样可以用做管理知识起码是技术管理知识的介绍,这篇Blog将作为本系列中的第一篇围绕管理知识的文章。由于本人现在并非管理角色,所以文章内容的正确性和合理性并不能完全保证。

剧情介绍:这篇文章中Tony的角色是一个部门leader,现在他想将Alex提拔为一个Project Leader,身处管理角色多年的Tony深知从技术角色走向管理角色决不是一蹴而就的事情,思维的转变需要的实践和耐心。下面是Tony和Alex的一次对话。Tony主要想了解一下Alex的在管理方面的涉猎以及存在的不足,顺便给Alex一些原则性的指导。

Tony : Alex, 你入司已经有3年了,而且一直是部门内部的技术骨干,你做的很好。
Alex : (心中很是欢喜,能获得Tony的好评真是一件值得高兴的事情,但Alex仍有些丈二和尚摸不到头)自己只是尽力而为罢了。
Tony : (Tony的慧眼当然很快的看出Alex的心理变化)Alex,你知道公司是如何选拔技术项目经理的么?
Alex : 我只知道我看到的项目经理曾经都是部门内部的技术拔尖的人。
Tony : 说的没错,在我们的公司中一直是“技优则管”,虽然这不是我们追求的理想办法,但是这些都决定于整个国内软件行业的“行情”以及软件行业从业者的思维特点。问你这样一个问题:“如果让你选择你所在项目的主管,你是选一个有技术背景的呢还是选一个毫不技术背景的呢”?
Alex : 我想我会选择前者,和一个有技术背景的主管可能沟通起来更加方便和容易。
Tony : 这也是现在大多数人的想法,即所谓的喜欢“内行管内行”,很多调查公司的调查均证明了这一点。现在国内的软件业发展很快,我们几乎不可能招聘一个有经验的职业的管理者并对之进行行业知识培训,这样的成本和风险都太高,我们唯一的方法就是从我们已有的优秀员工中选拔,对他们进行相关的管理知识培训,使之尽快的进入管理角色。
Alex : 原来如此,我以前还真的没想过这么多。
Tony : 想过进入管理者的行列么?你对管理了解多少呢?
Alex : (微笑)参加过很多次管理相关的培训,但是没有实践过,所以在理解和操作上还差很多。
Tony : 其实这次和你的谈话就是想了解些你自己的想法,顺便将我个人的一些经验和你做一些交流。
Alex : 我会珍惜这次机会的。
Tony : 我本人就是一个从技术角色转到管理角色的一个例子,所以我自己多多少少了解和经历了一些在转型过程中的问题,就这么多年来我的所见所闻而言,我觉得从技术角色到管理角色的转变关键是思维方式的转变。我们可以从一些现象中挖掘到这点。还是要问你一个问题。Alex,你觉得技术人员的特质有哪些呢?
Alex : 你问的是“什么是技术人员的特质”么?我不是十分能理解“特质”的含义,我就拿我自己以及我所看到的技术人员的行为共性说一下吧。我觉得大多数技术人员做事不够灵活,在某件具体的事情上容易钻牛角尖;他们很细心,做事思路清晰,关注细节,一步一步的向前走,也可以说有些按部就班(微笑);喜欢就事论事,一般不针对某个人;认为对就是对,错就是错。
Tony : 说的很好,看来你的观察力和总结能力都非常的不错。外国的很多调查公司都做过这方面的调查,你知道调查出的结果是什么么?
Alex : 什么呢,一定很有意思。
Tony : 结果显示技术人员的特质包括如下几个方面,你不妨对对号,看看你说的是不是都在其中。
 a)管事
 b)管细
 c)非黑即白
 d)对事不对人
 e)科学
 f)量化
 g)古板
 h)关注过程
 i)收敛思维
Alex : 这么多呀,最后两条我好像没有说出来。
Tony : “不识庐山真面目,只缘身在此山中”亚(Tony微笑着说)。你没体会到的两点“关注过程”和“收敛思维”恰恰是我认为比较重要的两点,也是技术人员在转型道路上比较难克服的两点。这也是我所说的思维上的东西。
Alex : 刚才想了一下自己在平时工作中的行为,还真的能对上号。
Tony : 是呀。比如一个客户让一个开发人员去开发一个ftp客户端,他可能马上接受任务,然后马上开始想我该如何设计和实现这个ftp客户端,而把客户这个关键的任务搁置在一旁,收敛性的思维决定了他根本意思到需要和客户沟通,询问客户到底需要一个什么样的ftp客户端。
Alex : 真是说到我的心坎里了。
Tony : 我这里同时说说管理人员的特质,你不妨对比一下。管理人员具有和技术人员截然不同的特质,他们:
 a)管人
 b)管粗
 c)非黑非白
 d)对事又对人
 e)艺术
 f)概念化
 g)灵活
 h)关注结果
 i)发散思维
Alex : 有这么大的差别亚。
Tony : 不用担心,这个世界上99.99%的管理者都不是天生的,他们也是在工作实践中一点一滴地学习成才的。再给你讲这样的一件事情,你来看看如何去处理?如果你是一个部门的销售经理,你让你的一个下属去写一份“xx产品的市场调研报告”,他欣然领命后下去了,不过时间不长他就回来了,并向你询问“xx章节该如何去写”,这时候你应该如何做呢?
Alex : 难道我事先没跟他讲清楚么?
Tony : 你讲清楚了,而且他还频频点头,好像已经听懂了的样子,但是事实就是他又回来问你了,你知道为什么吗?
Alex :(疑惑…)那我可能会再详细的告诉他该如何去写。
Tony : OK,这个问题解决了,之后他又反复地回来问你一些其他的你认为已经给他讲清了的问题,你该如何去办呢?
Alex : 会发生这样的情况么?那还不如我自己写这个报告了,这样多烦亚。
Tony : (微笑)这是一个典型的技术人员转型的障碍之一–“亲历亲为”。这里还蕴涵着一个管理的技巧问题“如何进行工作分派”。
Alex : 这么复杂亚。
Tony : 前面不说过么管理是一门艺术。“亲历亲为”是技术人员在转型过程中最容易犯的问题。技术管理者在转型后常常抱怨每天“忙碌而无成效”,其中“亲历亲为”是一个重要的原因,当然不是唯一的。“亲历亲为”导致的最直接的后果就是你每天忙得焦头烂额,而你的staff member每天却清闲的很。你想想在工作中是不是常有这样的事情发生呢?
Alex : 嗯,的确如此。私下里和同事朋友吃饭聊天时总是能听到这样的抱怨。
Tony : 如何能做到不亲历亲为呢?给你讲一个我自己的故事,这个故事其实和上面的故事差不多,它发生在我的个人助理身上。一次要参加一个会议,我让我的个人助理帮我完成这篇演讲稿,她完成初稿后发给我让我审阅,我给她指出若干个错误,然她重新修改,如此反复不下20次,在一天之内,在我告诉我的那位助理演讲稿通过了时候,她都快哭了。之后我再让这位助理些稿子,总是一遍就通过,我再也找不出其中的不足了。你要知道如果我自己来完成那篇讲演稿的话,可能只需要我30分钟的时间,而我却花了大量的时间来纠正她的不足。不妨想像一下如果没有那次的反复纠正,那个助理永远也不知道什么样的稿子是合格的。从中你能体会到什么吗?
Alex : 嗯,有些感觉了。
Tony : 古语说“强将手下无弱兵”,在现代的工作中,这句话可未必百分之百正确。
Alex : 是呀,强将的“亲历亲为”导致了“弱兵”的诞生。
Tony : 在培养部下迅速成长的同时,还要留心一些技巧问题,就如第一个例子中讲的那个销售经理,他在分派工作时并没有掌握足够的技巧,导致了后来的结果。
Alex : 分派工作还需要什么技巧么,直接告诉他做什么不就可以了么?
Tony : 事实上并非如此简单,你回想一下上面的那些例子在你的平时工作中是否发生过?我相信一定有的。一般来说分派工作可以使用6步法:
一、要向你的下属解释这个任务的重要性;
二、应该告诉他改做些什么而不是如何去做;
三、明确他的权力范围
四、确定Deadline,这里注意给自己留好退路
五、听取反馈,最简单的方法就是让他当面复述一遍任务是什么。
六、剩下的就是控制和跟踪了。

Alex : 哇,真是不听不知道呀。
Tony : 呵呵,回去不妨试试,你会看到立竿见影的效果。

办公室电话响了….

Tony : Alex ,这次谈话就到这吧,回去总结思考一下,有空儿我们再聊。
Alex : Ok。

Alex起身出门,头脑中浮现出一个优秀管理者的形象。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats