1.OS线程是可以被抢占的,也就是说,不会因为一个线程霸占住CPU不放而把其他线程都饿死。
那么轻量级线程是否可以被抢占。如果不能的话,如何防止饿死的情况。
2.OS线程有优先级。可以按照需求分配CPU时间。
3.OS线程提供了同步功能, 轻量级线程之间是否提供了同步功能?
4.轻量级线程之间是否有共享资源?如何来保护这些共享资源?OS线程采用的是锁机制,轻量级线程如何处理?
5.C/C++/D可以访问OS API,还有大量第三方类库。很难想象要求C/C++/D程序不使用任何库以及API,那么如何解决不可重入的OS API和类库?
今天看到了关于并发的大讨论,还有关于用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和类库?
这个问题可以参考一个非常古老的和轻量级线程非常类似的系统。那就是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> 写道:
> 我不需要全功能的调度器,所以如果一个线程霸住CPU不放,那我就合理地认为它一定很需要被运行,有什么理由一定要强行抢走CPU呢?大家都是为人民服务的,和谐社会不是很好吗?如果你很在意那个,用线程吧,也没什么不好。这个问题可以参考一个非常古老的和轻量级线程非常类似的系统。那就是Windows窗口的消息处理函数。
在一个Windows GUI应用程序里,同时有着大量的消息处理函数。通常情况下每个处理都非常短,很好没有任何问题。
在重负荷的情况下,系统的响应会明显迟钝。
有时候会发生一个窗口停止响应。也就是一个消息处理函数霸占了CPU。这个时候你唯一可做的就是杀掉整个程序。我相信所有的人都有这种经历吧?可见这种情况并不少见。
同步和锁的需求是由于并发造成的,和是否同一个操作系统线程完全没有关系。
你可以想象一下为什么在单CPU上也需要用锁。
用你的例子UserInfo user;如果在这个函数中,你需要访问共享对象,并且在query上下要维持该对象的一致性。你就需要加锁,因为在query中会发生线程切换,共享对象的状态会被破坏。
int ret = query("select * ...", 123, user);
if (ret != 0)
erlang的port干活本质上就是这种封装。因此你要干的活不会比做一个port简单。工作量可能会少点。
照pongba的说法就是所有的本质复杂性依然存在,最多消除了一些非本质复杂性。
在 07-12-7,Jian Wang< oxygen.j...@gmail.com> 写道:
> 今天看到了关于并发的大讨论,还有关于用C++/D实现轻量级线程。
> 我觉得在讨论具体的实现方法之前,必须要讨论一下轻量级线程是因为少了哪些功能才变轻的。
> OS线程之所以重,显然并不是因为OS的开发者愚蠢造成的。也就是说轻量级线程必定是应为少了某些重要的东西后,才变轻的。少了这些之后,对我们的编程有什么不利的影响。如何来消除不利的影响。
> 而对于这些问题的回答来说,照抄照搬ERLang是没有意义的。因为如果都和ERLang一样的话,为什么不用ERLang,而要用C++,D再实现一边呢?
>
> 1.OS线程是可以被抢占的,也就是说,不会因为一个线程霸占住CPU不放而把其他线程都饿死。
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> 写道:
>
>
我的意思是指shared_obj.func1();UserInfo user;shared_obj.func2();
int ret = query("select * ...", 123, user);
if (ret != 0)
这种情况。你能够完全避免吗?在C++里有很多隐含的共享。包括全局变量。Singleton模式。static变量等等。
如果完全避免的话,那么把这段程序转换成异步IO也相当容易。
在 07-12-9,lijie<cpu...@gmail.com> 写道:
> 是这样的,如果是单线程处理所有轻量级线程,这两个调用都不需要加锁。
即使是单线程调用,你也必须加锁,因为query中可能会发生调度。如果调用了其他线程的话,shared_obj的状态就可能会被破坏了。
再回到原来线程的时候。
shared_obj.func2的结果就是无法预测的。
如果不允许的话,却没有一种机制来保证,完全靠程序员自己来约束的话,不是非常容易出错吗?而且这个属于典型的不可重复再现的难debug问题。
在 07-12-9,lijie<cpu...@gmail.com> 写道:
>
你的意思是不是说,不允许出现下面的这种情况?
shared_obj.func1(); //shared_obj.member = 1int ret = query("select * ...", 123, user);shared_obj.func2(); //会用到shared_obj.member,并且只有等于1的时候才正确。
如果不允许的话,却没有一种机制来保证,完全靠程序员自己来约束的话,不是非常容易出错吗?而且这个属于典型的不可重复再现的难debug问题。
但问题是erlang通过语法来保证不会出现共享对象。
而你要求程序员记住不使用共享对象。这就是问题。
C++里非常容易出现隐性的共享。
前面已经讨论过了,你的这套解决方案是牺牲容错性来换取高性能的。
现在又引入了这样一个易错的机制。那么从工程的角度来讲就很不合理了。
简单地说,就是这套机制只适用于小项目。2-3个程序员的规模。
如果有20-30个程序员在轻量级线程上开发业务逻辑的话。那就肯定完蛋了。
在 07-12-9,lijie<cpu...@gmail.com> 写道:
>
>
你说的非常对
我赞同的你说法。
但问题是erlang通过语法来保证不会出现共享对象。
而你要求程序员记住不使用共享对象。这就是问题。
C++里非常容易出现隐性的共享。
前面已经讨论过了,你的这套解决方案是牺牲容错性来换取高性能的。
现在又引入了这样一个易错的机制。那么从工程的角度来讲就很不合理了。
简单地说,就是这套机制只适用于小项目。2-3个程序员的规模。
如果有20-30个程序员在轻量级线程上开发业务逻辑的话。那就肯定完蛋了。
很多在线程加锁方式时不致命的问题到了这里都变成致命问题了。
前面都讨论过了,比如一个线程堵死了。2个线程死锁了。影响是局部的。其他线程还能继续工作下去。time out的话,还可以杀掉他们回收一部分资源。
许多服务器会越跑越慢,消耗的内存越来越多,需要重起就是错误的累计。
而前面说的长锁也就是降低性能,不致命。
到了轻量级线程都变成致命问题了。
任何一个都会使服务器停止响应,或者崩溃。
在 07-12-9,lijie<cpu...@gmail.com> 写道:
>
>
在 07-12-10,Jian Wang<oxygen.j...@gmail.com> 写道:
--
Any complex technology which doesn't come with documentation must be the best
available.
在 07-12-10,lijie<cpu...@gmail.com> 写道:
我遇到的一些需要注意的地方主要有三点:
1. 调度逻辑的设计。如果对 coroutine 没有多少使用经验,切来切去可能就切晕了,如果不小心切到一个正在运行的 coroutine
就会出现一些奇形怪状的事情。这个问题可以靠简化设计来解决。只要每一个 coroutine
都只做一件很简单的事情,互相切换的关系也就不会复杂。通常我会有一个主 coroutine 跑事件循环,抓到事件以后切换到处理这个事件的
coroutine ,处理事件的 coroutine 处理完了再切回主 coroutine
。我所用的这种调度方式是基于我的需求(网络协议处理)来做的,如果是别的使用场合,可以用别的调度方式。
2. 异常。似乎在一些特别的情况,异常会有问题,这个跟平台有关。最好在用 coroutine 的代码里面就不要用异常了。
3. 调试。一般调试器查看线程的时候,可以看到所有的线程的调用栈。但是如果一个coroutine
没有正在运行,一般的调试器就不知道那个后台的 coroutine 的调用栈在哪里。我的意思是说,如果你有一个线程,创建了 3 个
coroutine ,然后你断下来,你只能看到当前正在运行的 coroutine 的调用栈。除了这个问题以外,有些调试器有时候对
coroutine 里面的断点支持有问题,打了断点却断不下来。还有,单步调试的时候,如果是切换到另一个 coroutine
里面,调试器就跟不进去。
首先,使用多个 coroutine 并不意味着一定是单线程,我在单线程中使用 coroutine 的。对于单线程对应多个 coroutine
的情况,共享资源的访问不是一个问题。因为共享资源并不会随时随地被修改,只可能是一个 coroutine 主动切换出去的这段时间才可能被修改。
我遇到的一些需要注意的地方主要有三点:
1. 调度逻辑的设计。如果对 coroutine 没有多少使用经验,切来切去可能就切晕了,如果不小心切到一个正在运行的 coroutine
就会出现一些奇形怪状的事情。
2. 异常。似乎在一些特别的情况,异常会有问题,这个跟平台有关。最好在用 coroutine 的代码里面就不要用异常了。
3. 调试。一般调试器查看线程的时候,可以看到所有的线程的调用栈。但是如果一个coroutine
没有正在运行,一般的调试器就不知道那个后台的 coroutine 的调用栈在哪里。我的意思是说,如果你有一个线程,创建了 3 个
coroutine ,然后你断下来,你只能看到当前正在运行的 coroutine 的调用栈。除了这个问题以外,有些调试器有时候对
coroutine 里面的断点支持有问题,打了断点却断不下来。还有,单步调试的时候,如果是切换到另一个 coroutine
里面,调试器就跟不进去。
它们的性能优劣其实主要体现在调度上。核心级的可以保证永远可以被抢占,用户级的则承诺高性能,核心级的付出的代价是高成本,用户级付出的代价是信任【不得不信任】线程的善意,Activation
Scheduler则试图把这两个方面的优点综合起来,避免两方面的缺点。
我觉得轻量级线程和重量级线程之争很像是用户级线程和核心级线程之争。
这可能不合大多数人的意,但是比较合我的意。