轻量级线程为什么轻。

245 views
Skip to first unread message

Jian Wang

unread,
Dec 7, 2007, 1:09:32 PM12/7/07
to pon...@googlegroups.com
今天看到了关于并发的大讨论,还有关于用C++/D实现轻量级线程。
我觉得在讨论具体的实现方法之前,必须要讨论一下轻量级线程是因为少了哪些功能才变轻的。
OS线程之所以重,显然并不是因为OS的开发者愚蠢造成的。也就是说轻量级线程必定是应为少了某些重要的东西后,才变轻的。少了这些之后,对我们的编程有什么不利的影响。如何来消除不利的影响。
而对于这些问题的回答来说,照抄照搬ERLang是没有意义的。因为如果都和ERLang一样的话,为什么不用ERLang,而要用C++,D再实现一边呢?

1.OS线程是可以被抢占的,也就是说,不会因为一个线程霸占住CPU不放而把其他线程都饿死。
那么轻量级线程是否可以被抢占。如果不能的话,如何防止饿死的情况。
2.OS线程有优先级。可以按照需求分配CPU时间。
3.OS线程提供了同步功能, 轻量级线程之间是否提供了同步功能?
4.轻量级线程之间是否有共享资源?如何来保护这些共享资源?OS线程采用的是锁机制,轻量级线程如何处理?
5.C/C++/D可以访问OS API,还有大量第三方类库。很难想象要求C/C++/D程序不使用任何库以及API,那么如何解决不可重入的OS API和类库?

lijie

unread,
Dec 7, 2007, 3:32:20 PM12/7/07
to pon...@googlegroups.com
On Dec 7, 2007 9:09 PM, Jian Wang <oxygen.j...@gmail.com> wrote:
今天看到了关于并发的大讨论,还有关于用C++/D实现轻量级线程。
我觉得在讨论具体的实现方法之前,必须要讨论一下轻量级线程是因为少了哪些功能才变轻的。
OS线程之所以重,显然并不是因为OS的开发者愚蠢造成的。也就是说轻量级线程必定是应为少了某些重要的东西后,才变轻的。少了这些之后,对我们的编程有什么不利的影响。如何来消除不利的影响。
而对于这些问题的回答来说,照抄照搬ERLang是没有意义的。因为如果都和ERLang一样的话,为什么不用ERLang,而要用C++,D再实现一边呢?

个人理论知识不扎实,瞎说几句吧。既然不是理论,所以我就不解释轻量级线程为啥轻了,简单列几条吧。

1、写起来最简单的并发程序是每连接一线程方式,各种操作都使用同步语法,而不是分解成多个异步操作。使用线程方式来可以简化程序编写。
2、系统中如果要处理十万个并发连接,操作系统通常无法创建这么多线程。
3、如果真能创建这么多线程,那么创建和销毁线程所花的时间也是相当可观的。我没测试过这个性能,不过根据小道消息,操作系统每秒能创建和销毁几万个线程。
4、erlang的轻量级线程轻到什么程度,我可以给你个数据。erlang每秒可以创建上百万个轻量级线程!fiber/ucontext虽然轻,但根据我的测试,它比erlang创建销毁进程的效率至少低了5倍。它的数量有没有限制我还不清楚。虽然比erlang差了许多,但一般的应用还是足够了。

线程的切换开销和erlang进程/fiber比起来还是比较大的,这是正常现象,通用的全功能调度器当然不如功能简单的调度器高效。我个人认为这个开销也并不是很大,唯一的弱点是线程数限制和创建销毁速度。如果我使用本地线程,我最终还是因为线程数限制而采用线程池,又回到那个状态机上去了。
 


1.OS线程是可以被抢占的,也就是说,不会因为一个线程霸占住CPU不放而把其他线程都饿死。
  那么轻量级线程是否可以被抢占。如果不能的话,如何防止饿死的情况。

我不需要全功能的调度器,所以如果一个线程霸住CPU不放,那我就合理地认为它一定很需要被运行,有什么理由一定要强行抢走CPU呢?大家都是为人民服务的,和谐社会不是很好吗?如果你很在意那个,用线程吧,也没什么不好。
 

2.OS线程有优先级。可以按照需求分配CPU时间。

使用轻量级线程,通常并不把耗时操作放在这里面,而是使用一种机制,把耗时操作丢给线程池,而又无损同步语法,所以这部分只是完成逻辑的调度。如果总是有耗时的计算操作,根本就不适合使用轻量级线程,丢进线程池去跑也一样。
 

3.OS线程提供了同步功能, 轻量级线程之间是否提供了同步功能?
 

4.轻量级线程之间是否有共享资源?如何来保护这些共享资源?OS线程采用的是锁机制,轻量级线程如何处理?
 
由于轻量级线程是运行在操作系统本地线程之上的,加锁可能导致线程死锁掉,所以通常不提供同步。共享?如果所有轻量级线程都运行于同一个操作系统线程上,还需要加什么锁呢?直接取就行了。多线程方式大家使用同步来通知 "这个操作我做完了",轻量级线程可以在完成操作时简单地交出CPU,不是更高效吗?

你说smp?要开多个本地线程来充分利用CPU?这是个麻烦,其实我并不想搞得这么麻烦,做成通用平台总会顾此失彼,所以我的理想实现是只做一个单线程的调度器,另一个线程池用来跑IO操作或者耗时的计算。这就是为什么轻量级线程是用来处理并发而不是并行问题。
 

5.C/C++/D可以访问OS API,还有大量第三方类库。很难想象要求C/C++/D程序不使用任何库以及API,那么如何解决不可重入的OS API和类库?

1、封装成异步操作。
2、不好封装的,用线程池来跑。

所有这些东西都是为了把逻辑部分简化成同步操作语法。


最后来回答这个问题:
因为如果都和ERLang一样的话,为什么不用ERLang,而要用C++,D再实现一边呢?

erlang使用现有的C库并不是很方便,比如mysql,用个port driver包装一遍?这活还是你来干吧。。干完了说一声,我还有个oracle等着。。呵呵。当然也可以只把业务部分做一个port driver,不过语言之间的胶合代码也是挺烦的。

如果是用C++来实现一个类似erlang的轻量级线程平台,就不用这么麻烦了,同一种语言嘛,传个对象,在线程池里面直接调用方法就很简单。例:

int query(const char* sql, size_t len, UserInfo& user);
这个函数用来在数据库中查询一条记录,实现很简单,生成一个调用对象(有闭包就爽了),丢进线程池(当然要多给个标识符,调度器在得到结果以后知道唤醒哪个轻量级的线程)。线程池里面可以直接操作UserInfo对象,就算它是在栈上的(轻量级线程栈)。线程池处理完了,通知主线程,主线程直接唤醒轻量级线程(当然可能要排队)。对于调用者来说,就是同步语法了:
UserInfo user;
int ret = query("select * ...", 123, user);
if (ret != 0)
   ...
完全看不出来中间竟然有个异步操作。绕了这么大个圈子就是为了这点小好处,不过对于复杂的逻辑来说程序开发难度可以降低几倍。 


最后一个不使用erlang的理由,实际上也是前面说过的,应该也是最大的阻力,开发人员不好找。

Jian Wang

unread,
Dec 7, 2007, 4:38:09 PM12/7/07
to pon...@googlegroups.com
> 我不需要全功能的调度器,所以如果一个线程霸住CPU不放,那我就合理地认为它一定很需要被运行,有什么理由一定要强行抢走CPU呢?大家都是为人民服务的,和谐社会不是很好吗?如果你很在意那个,用线程吧,也没什么不好。

这个问题可以参考一个非常古老的和轻量级线程非常类似的系统。那就是Windows窗口的消息处理函数。
在一个Windows GUI应用程序里,同时有着大量的消息处理函数。通常情况下每个处理都非常短,很好没有任何问题。
在重负荷的情况下,系统的响应会明显迟钝。
有时候会发生一个窗口停止响应。也就是一个消息处理函数霸占了CPU。这个时候你唯一可做的就是杀掉整个程序。我相信所有的人都有这种经历吧?可见这种情况并不少见。

如果你不介意因为一些意料外的耗时操作(这个很常见。意料外的东西太多了)影响整个系统的响应。也不介意因为一个线程的非致命BUG(因为某些意料外原因被block住了)杀掉100W个无辜线程的话,也就是说不需要提供持续服务的话,当然可以这么做。就像除了word,excel之类的会让我们半天白忙活的程序外。我们可以毫不犹豫地杀掉一个应用程序,然后重起他。

> 由于轻量级线程是运行在操作系统本地线程之上的,加锁可能导致线程死锁掉,所以通常不提供同步。共享?如果所有轻量级线程都运行于同一个操作系统线程上,还需要加什么锁呢?直接取就行了。多线程方式大家使用同步来通知
> "这个操作我做完了",轻量级线程可以在完成操作时简单地交出CPU,不是更高效吗?

同步和锁的需求是由于并发造成的,和是否同一个操作系统线程完全没有关系。
你可以想象一下为什么在单CPU上也需要用锁。
用你的例子


UserInfo user;
int ret = query("select * ...", 123, user);
if (ret != 0)

如果在这个函数中,你需要访问共享对象,并且在query上下要维持该对象的一致性。你就需要加锁,因为在query中会发生线程切换,共享对象的状态会被破坏。
由于你的理想状态是象写单线程程序一样来写这个函数,那么这种一致性是一个默认的规则。

> 1、封装成异步操作。
> 2、不好封装的,用线程池来跑。
>
> 所有这些东西都是为了把逻辑部分简化成同步操作语法。


erlang的port干活本质上就是这种封装。因此你要干的活不会比做一个port简单。工作量可能会少点。
照pongba的说法就是所有的本质复杂性依然存在,最多消除了一些非本质复杂性。

在 07-12-8,lijie<cpu...@gmail.com> 写道:

lijie

unread,
Dec 7, 2007, 5:27:05 PM12/7/07
to pon...@googlegroups.com
On Dec 8, 2007 12:38 AM, Jian Wang <oxygen.j...@gmail.com> wrote:
> 我不需要全功能的调度器,所以如果一个线程霸住CPU不放,那我就合理地认为它一定很需要被运行,有什么理由一定要强行抢走CPU呢?大家都是为人民服务的,和谐社会不是很好吗?如果你很在意那个,用线程吧,也没什么不好。

这个问题可以参考一个非常古老的和轻量级线程非常类似的系统。那就是Windows窗口的消息处理函数。
在一个Windows GUI应用程序里,同时有着大量的消息处理函数。通常情况下每个处理都非常短,很好没有任何问题。
在重负荷的情况下,系统的响应会明显迟钝。
有时候会发生一个窗口停止响应。也就是一个消息处理函数霸占了CPU。这个时候你唯一可做的就是杀掉整个程序。我相信所有的人都有这种经历吧?可见这种情况并不少见。

这个例子的任务(进程)之间并不了解,所以无法用协作的方式来工作。我的情况不同,使用者自己保证不做这种蠢事,否则后果自负,包括被迫杀掉进程。eve服务端不就是Stackless Python嘛。
 

同步和锁的需求是由于并发造成的,和是否同一个操作系统线程完全没有关系。
你可以想象一下为什么在单CPU上也需要用锁。

似乎我们说的不是同一件事。我的意思是我的所有轻量级线程跑在同一个线程里面,实际就是单线程程序,这些轻量级线程之间要加锁做什么呢?锁不就是为了处理线程间的共享嘛,我只有一个线程而已。。
 

用你的例子
UserInfo user;
int ret = query("select * ...", 123, user);
if (ret != 0)

如果在这个函数中,你需要访问共享对象,并且在query上下要维持该对象的一致性。你就需要加锁,因为在query中会发生线程切换,共享对象的状态会被破坏。

这个也是我说的理想状态,线程切换也没关系,只要保证通知不会被优化到修改user前面去就行(乱序执行)。锁并不是必须要使用的,只要有顺序保证,锁就可以避免,无锁环形队列不就是这样的吗。这个说远了,实际在这个query的黑盒子里,并不一定是共享地址,也可以是从线程池那边传一个结果过来,query里面被唤醒后把user赋值再返回。这个黑盒子里我可以有多种做法,但却提供同样的界面,那就是同步语法。
 


erlang的port干活本质上就是这种封装。因此你要干的活不会比做一个port简单。工作量可能会少点。
照pongba的说法就是所有的本质复杂性依然存在,最多消除了一些非本质复杂性。

和erlang比,好处1就是可以在同一种语言里做,好处2 是C++程序员毕竟不算少。。
 

lijie

unread,
Dec 8, 2007, 12:11:05 AM12/8/07
to pon...@googlegroups.com
补充一点。

erlang的调度器可以避免CPU被霸占的,因为它是以函数调用为调度单元的。由于你只能写递归而不能写循环,所以基本上也没办法霸占CPU。

我的应用是有区别的,要求使用者自己保证不霸占CPU,前提就是大家都别干蠢事。我的用户不是普通用户,而是经过训练的(其实就是我自己。。),清楚地知道如何把耗时任务做成异步操作,并且我打算提供一个简单的方式(这就是为什么我说有闭包就简单很多)。

win32真的有问题吗?它实际上很不错,只是被一些坏程序给破坏了而已,当然也包括我曾经写过的一个死循环。

关于加锁,我想说明的是使用多线程也不可以无节制的加锁。在我的设想里面,调度线程(同时也是运行轻量级线程的线程,前面称为主线程,修改一下)是不在任何地方加锁的,即便是和线程池之间的队列也可以使用无锁队列,只在线程池那一端加锁,因为竞争出现在线程池的线程之间。

发现我前面有贬低本地线程之嫌,所以纠正一下。本地线程创建、销毁、调度的效率都可以满足我的需求,唯一受限的是线程数量。

为什么同一个进程的线程之间需要加锁?原因就是操作系统总是强行切换,导致我总是有一些未完成的操作。简单例子,多线程共享一个以queue,如果总是能保证一个线程完整执行push/pop后再被切换,锁就可以免了。是的,这个假设是基于单CPU,多CPU也有问题,我认为多CPU只需要加上多个CPU的临界访问即可。

Atry

unread,
Dec 8, 2007, 6:16:50 AM12/8/07
to pon...@googlegroups.com
在 07-12-7,Jian Wang<oxygen.j...@gmail.com> 写道:

> 今天看到了关于并发的大讨论,还有关于用C++/D实现轻量级线程。
> 我觉得在讨论具体的实现方法之前,必须要讨论一下轻量级线程是因为少了哪些功能才变轻的。
> OS线程之所以重,显然并不是因为OS的开发者愚蠢造成的。也就是说轻量级线程必定是应为少了某些重要的东西后,才变轻的。少了这些之后,对我们的编程有什么不利的影响。如何来消除不利的影响。
> 而对于这些问题的回答来说,照抄照搬ERLang是没有意义的。因为如果都和ERLang一样的话,为什么不用ERLang,而要用C++,D再实现一边呢?
>
> 1.OS线程是可以被抢占的,也就是说,不会因为一个线程霸占住CPU不放而把其他线程都饿死。
> 那么轻量级线程是否可以被抢占。如果不能的话,如何防止饿死的情况。
> 2.OS线程有优先级。可以按照需求分配CPU时间。
coroutine 不需要优先级。coroutine
做的事情是逻辑上应该在一个线程连续处理的事情,但是通常本身并不是耗时操作。既然不是耗时操作,当然不需要什么优先级。

> 3.OS线程提供了同步功能, 轻量级线程之间是否提供了同步功能?
> 4.轻量级线程之间是否有共享资源?如何来保护这些共享资源?OS线程采用的是锁机制,轻量级线程如何处理?
线程需要加锁,为什么?因为线程切换的时机是不可知的。你需要靠锁来保证你做一个操作做到一半的时候不会被别的线程修改。 coroutine
则是你自己控制切换,当然不用锁。

Anson

unread,
Dec 8, 2007, 3:04:28 PM12/8/07
to pon...@googlegroups.com
。。。
关键在OS的设计。
觉得LINUX下面 线程再多也不会让其他线程太"饿"
win32下面好像就要差一点, 至于BSD不太清楚。

觉得这些内容还是多看看操作系统方面的书理解会比较透彻。 毕竟调度的权利在OS手中。。


在07-12-8,Atry < pop....@gmail.com> 写道:
在 07-12-7,Jian Wang< oxygen.j...@gmail.com> 写道:
> 今天看到了关于并发的大讨论,还有关于用C++/D实现轻量级线程。
> 我觉得在讨论具体的实现方法之前,必须要讨论一下轻量级线程是因为少了哪些功能才变轻的。
> OS线程之所以重,显然并不是因为OS的开发者愚蠢造成的。也就是说轻量级线程必定是应为少了某些重要的东西后,才变轻的。少了这些之后,对我们的编程有什么不利的影响。如何来消除不利的影响。
> 而对于这些问题的回答来说,照抄照搬ERLang是没有意义的。因为如果都和ERLang一样的话,为什么不用ERLang,而要用C++,D再实现一边呢?
>
> 1.OS线程是可以被抢占的,也就是说,不会因为一个线程霸占住CPU不放而把其他线程都饿死。

所有的线程和进程都是可抢占的吧, 这个东西早就研究的没有东西了, 看怎么实现的。。。
time-sharing OS 都可以保证这一点。。。

Jian Wang

unread,
Dec 9, 2007, 7:10:38 AM12/9/07
to pon...@googlegroups.com
> 这个例子的任务(进程)之间并不了解,所以无法用协作的方式来工作。我的情况不同,使用者自己保证不做这种蠢事,否则后果自负,包括被迫杀掉进程。eve服务端不就是Stackless
> Python嘛。

erlang的一个基本假设就是任何人都会干蠢事。
牺牲可靠性换取性能当然在某些时候是可取的。

>
> 这个也是我说的理想状态,线程切换也没关系,只要保证通知不会被优化到修改user前面去就行(乱序执行)。锁并不是必须要使用的,只要有顺序保证,锁就可以避免,无锁环形队列不就是这样的吗。这个说远了,实际在这个query的黑盒子里,并不一定是共享地址,也可以是从线程池那边传一个结果过来,query里面被唤醒后把user赋值再返回。这个黑盒子里我可以有多种做法,但却提供同样的界面,那就是同步语法。


我的意思是指
shared_obj.func1();


UserInfo user;
int ret = query("select * ...", 123, user);
if (ret != 0)

shared_obj.func2();
这种情况。你能够完全避免吗?在C++里有很多隐含的共享。包括全局变量。Singleton模式。static变量等等。
如果完全避免的话,那么把这段程序转换成异步IO也相当容易。

在 07-12-8,lijie<cpu...@gmail.com> 写道:
>
>

lijie

unread,
Dec 9, 2007, 11:11:06 AM12/9/07
to pon...@googlegroups.com


2007/12/9 Jian Wang <oxygen.j...@gmail.com>:

我的意思是指
shared_obj.func1();
UserInfo user;
int ret = query("select * ...", 123, user);
if (ret != 0)
shared_obj.func2();
这种情况。你能够完全避免吗?在C++里有很多隐含的共享。包括全局变量。Singleton模式。static变量等等。
如果完全避免的话,那么把这段程序转换成异步IO也相当容易。

是这样的,如果是单线程处理所有轻量级线程,这两个调用都不需要加锁。如果是必须要加锁的,特别是和其它线程有共享,可以把上面这整段代码丢进线程池运行。最终要达到的目的是不在调度线程中加锁,erlang的无锁也只是调度线程无锁,我的想法基本上也是一样。 

Jian Wang

unread,
Dec 9, 2007, 12:05:35 PM12/9/07
to pon...@googlegroups.com
> 是这样的,如果是单线程处理所有轻量级线程,这两个调用都不需要加锁。
即使是单线程调用,你也必须加锁,因为query中可能会发生调度。如果调用了其他线程的话,shared_obj的状态就可能会被破坏了。
再回到原来线程的时候。
shared_obj.func2的结果就是无法预测的。

在 07-12-9,lijie<cpu...@gmail.com> 写道:

lijie

unread,
Dec 9, 2007, 12:44:22 PM12/9/07
to pon...@googlegroups.com

2007/12/9 Jian Wang <oxygen.j...@gmail.com>:

> 是这样的,如果是单线程处理所有轻量级线程,这两个调用都不需要加锁。
即使是单线程调用,你也必须加锁,因为query中可能会发生调度。如果调用了其他线程的话,shared_obj的状态就可能会被破坏了。
再回到原来线程的时候。
shared_obj.func2的结果就是无法预测的。
 
你说的是对于多个调用操作需要原子化?这个是事务问题,并不是线程安全问题。对于协作方式,开发者要自己保证不会在一个操作的过程中中断,或者是安全地中断,所以不需要加锁。多线程方式由于是操作系统强行调度,加锁是无法避免的。或许会有你说的这样的问题,但同样的问题总是有不同的方法来处理,多线程的编写方法并不适合直接搬到这里。


Jian Wang

unread,
Dec 9, 2007, 1:12:07 PM12/9/07
to pon...@googlegroups.com
你的意思是不是说,不允许出现下面的这种情况?
shared_obj.func1(); //shared_obj.member = 1

int ret = query("select * ...", 123, user);
shared_obj.func2(); //会用到shared_obj.member,并且只有等于1的时候才正确。

如果不允许的话,却没有一种机制来保证,完全靠程序员自己来约束的话,不是非常容易出错吗?而且这个属于典型的不可重复再现的难debug问题。

在 07-12-9,lijie<cpu...@gmail.com> 写道:
>

lijie

unread,
Dec 9, 2007, 1:33:29 PM12/9/07
to pon...@googlegroups.com


2007/12/9 Jian Wang <oxygen.j...@gmail.com>:

你的意思是不是说,不允许出现下面的这种情况?
shared_obj.func1(); //shared_obj.member = 1
int ret = query("select * ...", 123, user);
shared_obj.func2(); //会用到shared_obj.member,并且只有等于1的时候才正确。

如果不允许的话,却没有一种机制来保证,完全靠程序员自己来约束的话,不是非常容易出错吗?而且这个属于典型的不可重复再现的难debug问题。

不是没有办法,是这个例子不合理。如果是多线程你该如何来做?整个过程加上锁,而不管query这个耗时操作?如果多线程都是串行化来低效运行的,为什么要求单线程进行这种调度呢?这里先假定你这样做是合理的,那么多线程方式这样使用共享对象的目的是什么?同样的操作可以有不同的做法,长锁是绝对应该避免的。

这个例子用erlang来说明吧,因为实现是一样的,把shared_obj对象单独跑一个"进程",当"进程A"向它发送"func1"时,它改变状态并进入receive状态,匹配来自"进程A"的消息。"进程A"在发送消息以后也进入receive状态,等待来自shared_obj的回复。这时候"进程B"发送消息"func1"时,shared_obj并不响应,它只接受来自"进程A"的func2消息。使用协作方式来模拟你要求的这个"锁"。

Jian Wang

unread,
Dec 9, 2007, 1:53:52 PM12/9/07
to pon...@googlegroups.com
你说的非常对
我赞同的你说法。

但问题是erlang通过语法来保证不会出现共享对象。
而你要求程序员记住不使用共享对象。这就是问题。
C++里非常容易出现隐性的共享。

前面已经讨论过了,你的这套解决方案是牺牲容错性来换取高性能的。
现在又引入了这样一个易错的机制。那么从工程的角度来讲就很不合理了。
简单地说,就是这套机制只适用于小项目。2-3个程序员的规模。
如果有20-30个程序员在轻量级线程上开发业务逻辑的话。那就肯定完蛋了。

在 07-12-9,lijie<cpu...@gmail.com> 写道:
>
>

lijie

unread,
Dec 9, 2007, 2:01:34 PM12/9/07
to pon...@googlegroups.com


2007/12/9 Jian Wang <oxygen.j...@gmail.com>:

你说的非常对
我赞同的你说法。

但问题是erlang通过语法来保证不会出现共享对象。
而你要求程序员记住不使用共享对象。这就是问题。
C++里非常容易出现隐性的共享。

前面已经讨论过了,你的这套解决方案是牺牲容错性来换取高性能的。
现在又引入了这样一个易错的机制。那么从工程的角度来讲就很不合理了。
简单地说,就是这套机制只适用于小项目。2-3个程序员的规模。
如果有20-30个程序员在轻量级线程上开发业务逻辑的话。那就肯定完蛋了。

20-30个开发人员的项目,贴近底层的东西通常也是少数几个开发人员在做,包装以后再给做业务逻辑的开发人员使用。比如这里的shared_obj对象可以做成兼容这个平台的,然后直接使用就是了。即使是线程方式,虽然已经有这么多成熟的东西可用,出错也是非常多的,本质上没什么不同。

Jian Wang

unread,
Dec 9, 2007, 4:34:38 PM12/9/07
to pon...@googlegroups.com
问题在于不需要底层代码,业务代码就可以摧毁系统了。
比如引入了一个static变量。或者传递了一个指针或引用。

很多在线程加锁方式时不致命的问题到了这里都变成致命问题了。
前面都讨论过了,比如一个线程堵死了。2个线程死锁了。影响是局部的。其他线程还能继续工作下去。time out的话,还可以杀掉他们回收一部分资源。
许多服务器会越跑越慢,消耗的内存越来越多,需要重起就是错误的累计。
而前面说的长锁也就是降低性能,不致命。

到了轻量级线程都变成致命问题了。
任何一个都会使服务器停止响应,或者崩溃。

在 07-12-9,lijie<cpu...@gmail.com> 写道:
>
>

Atry

unread,
Dec 10, 2007, 6:30:56 AM12/10/07
to pon...@googlegroups.com
这个帖子我实在看不下去了。
我用过 coroutine ,你所说的问题我都没碰到过。而我碰到过的问题你根本提都没提。

在 07-12-10,Jian Wang<oxygen.j...@gmail.com> 写道:

xxmplus

unread,
Dec 10, 2007, 8:05:05 AM12/10/07
to pon...@googlegroups.com
那就现身说法吧:)

--
Any complex technology which doesn't come with documentation must be the best
available.

lijie

unread,
Dec 10, 2007, 8:18:41 AM12/10/07
to pon...@googlegroups.com
期待。我正需要这方面的经验。

2007/12/10 xxmplus <xxm...@gmail.com>:

pi1ot

unread,
Dec 10, 2007, 9:20:21 AM12/10/07
to TopLanguage
刚才在javaeye看到一个帖子:

http://www.javaeye.com/news/555
结论: 在相同硬件条件下,ErlyWeb是Rails2.0响应速度的47倍

测试本身是否有代表性先不说,后面一个回帖:

yehs220 10 分钟前
两者的开发效率估计得反过来~

On 12月7日, 下午11时32分, lijie <cpun...@gmail.com> wrote:
最后一个不使用erlang的理由,实际上也是前面说过的,应该也是最大的阻力,开发人员不好找。

Jian Wang

unread,
Dec 10, 2007, 1:06:24 PM12/10/07
to pon...@googlegroups.com
我也很期待。

在 07-12-10,lijie<cpu...@gmail.com> 写道:

Atry

unread,
Dec 11, 2007, 8:36:35 AM12/11/07
to pon...@googlegroups.com
首先,使用多个 coroutine 并不意味着一定是单线程,我在单线程中使用 coroutine 的。对于单线程对应多个 coroutine
的情况,共享资源的访问不是一个问题。因为共享资源并不会随时随地被修改,只可能是一个 coroutine 主动切换出去的这段时间才可能被修改。

我遇到的一些需要注意的地方主要有三点:
1. 调度逻辑的设计。如果对 coroutine 没有多少使用经验,切来切去可能就切晕了,如果不小心切到一个正在运行的 coroutine
就会出现一些奇形怪状的事情。这个问题可以靠简化设计来解决。只要每一个 coroutine
都只做一件很简单的事情,互相切换的关系也就不会复杂。通常我会有一个主 coroutine 跑事件循环,抓到事件以后切换到处理这个事件的
coroutine ,处理事件的 coroutine 处理完了再切回主 coroutine
。我所用的这种调度方式是基于我的需求(网络协议处理)来做的,如果是别的使用场合,可以用别的调度方式。
2. 异常。似乎在一些特别的情况,异常会有问题,这个跟平台有关。最好在用 coroutine 的代码里面就不要用异常了。
3. 调试。一般调试器查看线程的时候,可以看到所有的线程的调用栈。但是如果一个coroutine
没有正在运行,一般的调试器就不知道那个后台的 coroutine 的调用栈在哪里。我的意思是说,如果你有一个线程,创建了 3 个
coroutine ,然后你断下来,你只能看到当前正在运行的 coroutine 的调用栈。除了这个问题以外,有些调试器有时候对
coroutine 里面的断点支持有问题,打了断点却断不下来。还有,单步调试的时候,如果是切换到另一个 coroutine
里面,调试器就跟不进去。

lijie

unread,
Dec 11, 2007, 2:43:24 PM12/11/07
to pon...@googlegroups.com


2007/12/11 Atry <pop....@gmail.com>:

首先,使用多个 coroutine 并不意味着一定是单线程,我在单线程中使用 coroutine 的。对于单线程对应多个 coroutine
的情况,共享资源的访问不是一个问题。因为共享资源并不会随时随地被修改,只可能是一个 coroutine 主动切换出去的这段时间才可能被修改。

我遇到的一些需要注意的地方主要有三点:
1. 调度逻辑的设计。如果对 coroutine 没有多少使用经验,切来切去可能就切晕了,如果不小心切到一个正在运行的 coroutine
就会出现一些奇形怪状的事情。

如果是单线程的的调度器,这个问题似乎不存在吧,既然切换出去了自然就不在运行中了。
 

2. 异常。似乎在一些特别的情况,异常会有问题,这个跟平台有关。最好在用 coroutine 的代码里面就不要用异常了。

我还打算用异常来简化代码编写呢,看样子要另做打算啦。。不知道longjmp/setjmp行不行。
 

3. 调试。一般调试器查看线程的时候,可以看到所有的线程的调用栈。但是如果一个coroutine
没有正在运行,一般的调试器就不知道那个后台的 coroutine 的调用栈在哪里。我的意思是说,如果你有一个线程,创建了 3 个
coroutine ,然后你断下来,你只能看到当前正在运行的 coroutine 的调用栈。除了这个问题以外,有些调试器有时候对
coroutine 里面的断点支持有问题,打了断点却断不下来。还有,单步调试的时候,如果是切换到另一个 coroutine
里面,调试器就跟不进去。

调试本来也很少用,除了底层平台开发时需要以外,其它时候还是少用调试。我还是习惯log/trace排错。
 

Atry

unread,
Dec 11, 2007, 7:03:48 PM12/11/07
to pon...@googlegroups.com
在 07-12-11,lijie<cpu...@gmail.com> 写道:

>
>
> 2007/12/11 Atry <pop....@gmail.com>:
> > 首先,使用多个 coroutine 并不意味着一定是单线程,我在单线程中使用 coroutine 的。对于单线程对应多个 coroutine
> > 的情况,共享资源的访问不是一个问题。因为共享资源并不会随时随地被修改,只可能是一个 coroutine
> 主动切换出去的这段时间才可能被修改。
> >
> > 我遇到的一些需要注意的地方主要有三点:
> > 1. 调度逻辑的设计。如果对 coroutine
> 没有多少使用经验,切来切去可能就切晕了,如果不小心切到一个正在运行的 coroutine
> > 就会出现一些奇形怪状的事情。
>
> 如果是单线程的的调度器,这个问题似乎不存在吧,既然切换出去了自然就不在运行中了。
如果逻辑编写得不对的话,是存在这个问题的。比如说正在运行中的 coroutine 切换到自己里面。尤其用 Unix 的
makecontext 系列 API 的时候,每一个 ucontext_t 结构保存的是一个状态快照(实际上就是当时的寄存器值),API
层次是可以多次切换到同一个 context 的。

Atry

unread,
Dec 11, 2007, 7:08:50 PM12/11/07
to pon...@googlegroups.com
在 07-12-11,lijie<cpu...@gmail.com> 写道:
>
>
> 2007/12/11 Atry <pop....@gmail.com>:
> > 首先,使用多个 coroutine 并不意味着一定是单线程,我在单线程中使用 coroutine 的。对于单线程对应多个 coroutine
> > 的情况,共享资源的访问不是一个问题。因为共享资源并不会随时随地被修改,只可能是一个 coroutine
> 主动切换出去的这段时间才可能被修改。
> >
> > 我遇到的一些需要注意的地方主要有三点:
> > 1. 调度逻辑的设计。如果对 coroutine
> 没有多少使用经验,切来切去可能就切晕了,如果不小心切到一个正在运行的 coroutine
> > 就会出现一些奇形怪状的事情。
>
> 如果是单线程的的调度器,这个问题似乎不存在吧,既然切换出去了自然就不在运行中了。
>
> >
> > 2. 异常。似乎在一些特别的情况,异常会有问题,这个跟平台有关。最好在用 coroutine
> 的代码里面就不要用异常了。
>
> 我还打算用异常来简化代码编写呢,看样子要另做打算啦。。不知道longjmp/setjmp行不行。
>
异常那个问题可能是我自己搞错了也不一定。反正我用的时候,异常似乎工作得不正常。但我没有写测试代码去证实,只是简单的在相关的地方不用异常而已。

up duan

unread,
Dec 17, 2007, 7:38:26 AM12/17/07
to pon...@googlegroups.com
我觉得轻量级线程和重量级线程之争很像是用户级线程和核心级线程之争。

它们的性能优劣其实主要体现在调度上。核心级的可以保证永远可以被抢占,用户级的则承诺高性能,核心级的付出的代价是高成本,用户级付出的代价是信任【不得不信任】线程的善意,Activation
Scheduler则试图把这两个方面的优点综合起来,避免两方面的缺点。

Anson

unread,
Dec 19, 2007, 5:50:00 AM12/19/07
to pon...@googlegroups.com
其实这个争论有些偏离主旨,
当然也和操作系统有关, 也和设计有关, 如果在设计中滥用线程其实也不好,
有些操作系统的process_creating的代价太大, 人们才会去用thread但是线程带来的问题也同时存在。

所以我觉得能不用多建一个线程解决问题就少建一个线程。



On Dec 17, 2007 3:38 PM, up duan < fix...@gmail.com> wrote:
我觉得轻量级线程和重量级线程之争很像是用户级线程和核心级线程之争。
核心级线程一般是kernel才会有的, 做一些批处理类似的工作, 一般用到的都是用户级线程,
性能的话, 不应该责怪调度太多, 毕竟在这个平台上的程序都用的一样的调度法则,
所以问题应该是在设计上把。

up duan

unread,
Dec 19, 2007, 9:45:44 AM12/19/07
to pon...@googlegroups.com
Delphi有一个非常有名的网络相关类库Indy,其作者的言论颇有代表性:多线程【阻塞式】比异步模式容易理解的多。现在我们的焦点是降低线程的开销,而不是改造成不直观的异步模式。

这可能不合大多数人的意,但是比较合我的意。

Reply all
Reply to author
Forward
0 new messages