smcpu的线程有什么用秒包怎么用?

小弟新手 ,请大家看看我这个做法的线程怎么销毁呢?
我想在Test跳转或者关闭的时候&销毁里面创建的线程new&Thread(),不知道该怎么操作,望大家不吝赐教啊!!!
代码如下:
public&class&Test&extends&Activity&implements&OnClickListener{
private&Button&btnT
public&final&static&int&LOAD_PROGRESS&=0;
&&&&Handler&handler&=&new&Handler(){&&
&&&&&&&&public&void&handleMessage(Message&msg){&&
&&&&&&&&&&&&switch(msg.what){&&
&&&&&&&&&&&&case&LOAD_PROGRESS:&&
&&&&&&&&&&&& &
&&&&&&&&&&&&&&&&&&Toast.makeText(getApplicationContext(),&(String)msg.obj,Toast.LENGTH_SHORT).show();&
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&
&&&&&&&&&&&&}&&
&&&&&&&&&&&&super.handleMessage(msg);&&
&&&&&&&&}&&
protected&void&onCreate(Bundle&savedInstanceState)&{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnTest=(Button)findViewById(R.id.btnSysconTest);
btnTest.setOnClickListener(this);
public&void&onClick(View&v)&{
switch&(v.getId())&{
case&R.id.Test:
&&&& &Test();
&&&&private&void&Test()
&&&& &&&&&
&&&& new&Thread()
&&&&&&&&&&&&&&&{&&
&&&&&&&&&&&&&&&&public&void&run()
&&&&&&&&&&&&&&&&&{&&
&&&&&&&&&&&&&&&&&&Message&msg&=&new&Message();&
&&&&&&&&&&&&&&&&&&--------&一些操作&------------
&&&&&&&&&&msg.what&=&LOAD_PROGRESS;
&&&&&&& &&msg.obj=&aaaaaaa&;
&&&&&&& &&handler.sendMessage(msg); &
&&&&&&&&&&&&&&&&}&&
&&&&&&&&&&&&}.start();&&
&&&&@Override&
&&&&protected&void&onDestroy()&{&&
&&&&&&&&//是在这里将线程销毁掉吗???&&怎么做呢???
&&&&&&&&super.onDestroy();&&
------解决方案--------------------
本来是handler.removeCallbacks(线程名);但是你没定义你的线程,你怎么在&onDestroy()&方法里面销毁了
------解决方案--------------------
boolean&isStop&=&
Thread&test&=&new&Thread(){
&&&&public&void&run()&{&&
&&&&&&&&while(!isStop)&{
&&&&&&&&&&&逻辑处理
在onDestroy中
test.interrupt();
------解决方案--------------------
你这个线程需要销毁吗?自己会销毁吧
------解决方案--------------------
线程还能销毁?
你这个代码不需要做什么...
新手请给我个思路 新手面临的问题。
想做一个手机上运行的程序,初步是安卓手机上,功能就是能连接到远程的MSSQL数据库,可以用户名和密码登陆,然后对记录进行增删改的基本操作,我不知道需要哪些开发工具和软件,用到什么技术?请过来人给我讲一下,想尽快以例子的方式入门!谢谢!
------解决方案-------------------- 通常不会在手机上直接连数据库。
搭建个web服务器,手机通
新手求问,大家不要在意我的代码- -
importandroid.app.A importandroid.os.B importandroid.widget.*; importandroid.util.L importandroid.view.V importandroid.wid
大家帮我看看我这个思路对不对?
本帖最后由 mykoss 于
10:09:25 编辑
我想做这样一个应用:
以一个关键字获取百度图片搜索的结果,并把结果图片展现在一个GridView上:
1:将关键字包装成一个url获取百度图片搜索结果网页的xml
2:解析这个xml得到一系列图片的url,将这些url放到一个字符型数组中
我用imagePicker选到image 这个image的路径怎么取到呢 我用imagePicker选到image 这个image的路径怎么取到呢,我想把它的路径存到sqlite中去,并且还能够从数据库中取出来,应该要怎么写。 [code=C/C++][/code]- (void)imagePickerController:(UIImagePickerController *)picker&nbs
android sqlite数据库怎样写带条件的查询语句?我是新手,求各位帮帮忙,谢谢 下面是我的表结构:
privatevoidcreateMusicTable(SQLiteDatabasedb){ db.execSQL(&createtableifnotexistsmusi
老师让做课程设计,新手请各位大神出招 这不,老师让弄个课程设计,我选的题目是做个通讯录出来。
前不久看了一些Objective-C编程,想弄个iPhoneapp的通讯录出来。
各位大神,小弟不才,各位能否给小弟指指路?
------解决方案-------------------- 简单的通讯录基本上是增删改查吧
基本控件(UITextField、UIButton什么的)、表
老师让做课程设计,新手请各位大神出招 这不,老师让弄个课程设计,我选的题目是做个通讯录出来。
前不久看了一些Objective-C编程,想弄个iPhoneapp的通讯录出来。
各位大神,小弟不才,各位能否给小弟指指路?
iphone 编程 app
------解决方案-------------------- 简单的通讯录基本上是增删改查吧
基本控件(
有两个界面的布局效果简图, 求实现思路, 提供代码更好, 我是新手
本帖最后由 foranz 于
10:48:24 编辑
要分别实现下面两个图的效果,因为刚用android不久,又要急着交货,所以请各位大神帮帮忙,能给出代码的本人将感激不尽!
图中上下两个箭头是图片来的
------解决思路--------
我新手,各位前辈有没有可以运行的简单实现:注册登录+Mysql+tomcat的源代码呀?求一份
一定要是可以运行的啊!/data/621816之前在网上找的一份,修改了里面的一些东西后,并不能运行
求一份小的demo....
新手一枚,有木有人帮我看下问题在哪啊 为了显示一个textview,在mainactivity中加入了下面代码
TextViewtv=(TextView)findViewById(R.id.tv);
tv.setText(Html.fromHtml(&aaaa&));
在fragment_main中加入下面代码
新手Hello World中IB的连线问题... 小弟我初学Iphone开发,
创建好Window.xib后,运行没事,但一与Window.h的控件相连线,空心的点变成实心的以后,再运行,就出现以下问题.
1:49:36.032FirstWin[]***Terminatingapp
超级新手求助helloworld 小弟今天刚刚开始接触,照着官网说的整了一个helloworld,结果就是不行。跪求指点~~~~Orz
以下是错误代码,我表示完全晕菜了。。。
GNUgdb6.3.50-(Appleversiongdb-1708)(ThuNov3&n
帮我看看这个编译出错 PersonService.java
publicPersonfind(Integerid){ SQLiteDatabasedb=dbOpenHelper.getReadableDatabase(); Cursorcursor=db.rawQuery(&select*form&nb
小弟愚钝,求大神帮看看这个json用sbjson怎么解析 functionString=[{\&TYPE\&:\&打印机\&,
\&CONFIGDATA\&:
通过这个方法返回一个Cursor,Cursor关闭后我怎么才能关闭查询出这个Cursor的db呢 publicCursorquery(Uriuri,String[]projection,Stringselection,
String[]selectionArgs,Strings
大家有人在做百度地图的开发吗?谁能帮我看看PopupOverlay这里的showPopup怎么了,核心代码和异常已贴上,感激不尽
privateMapViewmMapView= mPopupView=LayoutInflater.from(BaiduMapActivity.this).inflate(R.layo
android布局高手帮我看一下这个效果怎么布局
本帖最后由 qiuqingpo 于
13:55:17 编辑
直接上图片,下面的四个图片的布局
------解决方案-------------------- 最下面的那个4个图片吗
下面这个差不多能达到你的效果
&LinearLayo
新手求助:我注册的99美元公司型开发者账户,能在windows中VM7下安装的IOS系统里,让程序在真机上跑吗? 你好,如题, 我注册了99美元公司型的用户。 但是我没买苹果的电脑,只在windows里面装了虚拟机,然后装了苹果的系统, 请问这种情况下我能够把开发的IOS程序直接在我的IPhone4上跑,而不是只在虚拟机上跑吗? 或者说必须买个苹果的电脑才行?
------解决方案------
IOS集成支付宝遇到问题,高手指导我下,小弟不胜感谢 我参考官方给的Demo,集成到我的iphone程序里,可以跳到支付宝的客户端,输入账号,密码,
然后把收到短信通知的验证码输入完成交易,总是显示“订单签名错误,请检查PARTNER对应的密钥是否正确”
我仔细检查,实在不知道是啥问题,有高手遇到过这个问题吗?教教我,谢谢!
------解决方案-------------------- di
IOS集成支付宝遇到问题,高手指导我下,小弟不胜感谢 我参考官方给的Demo,集成到我的iphone程序里,可以跳到支付宝的客户端,输入账号,密码, 然后把收到短信通知的验证码输入完成交易,总是显示“订单签名错误,请检查PARTNER对应的密钥是否正确” 我仔细检查,实在不知道是啥问题,有高手遇到过这个问题吗?教教我,谢谢!
------解决方案-------------------- din
请高手帮我看下如下Android代码,问题在哪里
第28行报空指针。求救求救,我是新手。新新手。 手机已经root了。
packagecom.example.
importjava.io.FileInputS importjava.io.FileNotFoundE importjav
为什么我这个线程不进去!? try{
newThread(newRunnable(){
publicvoidrun(){
date=ud.getSystemDate();
Log.i(&limit&qu
新手开发tabhost的时候求助 布局文件:
&RelativeLayoutxmlns:android=&/apk/res/android&
xmlns:tools=&/tools&
谁能发给我个lucene和DB整合的项目
谁能发给我个lucene和DB整合的项目,小弟最近学习这个,但是没一个好的例子,而且网上的很多就很老了。 小弟邮箱
新手请教个问题,有关cocos2d-x 最近玩上一款手游,部落站争,无意当中发现有个游戏助手,得知里面用lua脚本集成编写的,请教下大侠们,像这种漂浮在游戏内的游戏助手能用cocos2d开发么?小弟刚接触手机app开发,对这些还不太懂,请大侠们给指条路,用cocos2d开发手游辅助是否可行?该用cocos的哪个产品开发合适呢?小弟谢过了!…
------解决思路-----------------
帮忙看看我这个selector是不是有问题,谢谢 我写了个简单的selector,但是调用时出错了,请大家看看我这个selector是不是有问题,谢谢
&?xmlversion=&1.0&encoding=&utf-8&?& &selectorxmlns:android=&http://s
(新手)这个错误是什么意思? 新建以后直接运行那个helloworld
Yourprojectcontainserror(s),pleasefixthembeforerunningyourapplication.
错误几乎都是error:&nbsp
我用数据库连接池出现的一个错误大家看看怎么回事
Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: 到主机 的 TCP/IP 连接失败。 java.net.ConnectException: Connection refused: connect at com.microsoft.sqlserver.jdbc.SQL
android开发(27) 看看我的手机里都有什么传感器
想看看我的HTC ONE x 具有什么传感器。写个代码RUN一下。
代码很简单,直接贴了
MainActivity = = SensorManager sm =&Sensor& allSensors == sb.append(&\t该手机有& + allSensors.s
新手求教 请问在windows下虚拟机上开发iphone会不会很卡,电脑4g内存,双核cpu。另外还有虚拟机上可不可以用真机调试?
------解决方案-------------------- 会很卡,可以调试,不可发布
------解决方案-------------------- 我开始用的虚拟机,很卡,后来没办法买了一台MAC的笔记本。 建议楼主要开发还是要在真机上,速度快些。
本教程属于摄影新手入门教程,向大家介绍了6个日常的新手摄影练习,非常值得新手朋友阅读学习,转发过来,希望对大家有所帮助! 一、 学习使用点测光
现如今数码相机的测光功能虽然很方便,但事实上在某些情况仍然会有失败的时候,而学习使用点测光就可以自行判断最合适的曝光值。另外,使用点测光的方法不是把测光部份在放在拍摄主体上,而是把测光点放在画面上你认为是属于「中间灰」(亦即拥有中间曝光值的地方)的地方上
新手求教camera程序解读 求教各位高手,这段代码是做什么用的,拜托大概加点注释, 比如是做什么用的,用什么类什么方法,选到的文件保存在哪里 这样的,小弟拜谢了!! #import &CameraViewController.h&
@implementation CameraViewController @synthesize imageV @synthesize
文章开头就列举了那么多联系方式,难免会让大家感觉有点AD的意味,但是不容质疑的是,默默的确有那么丁点的表现欲^_^,虽然有时候过于细致会被人说婆妈,但是幸好这种 细致对于编程来说,还是蛮有益的!
从默默自己向别人问怎么学PHP开始,到后来不少人又来问默默怎么学PHP,不管默默是新手,还是老鸟,似乎总是感觉摸不出一条清晰的脉络来,不过,默默既然学会了PHP,那么我走的这条
为什么我从webservice获取这样的数据,谁能告诉我怎么样解析?
------解决方案-------------------- 直接用正则解析
或将数据替换为标准的json格式之后再解析。
------解决方案-------------------- 看下你的url里是不是有个jsoncallback=anyType这样的参数
------解决方案-----------------
android 下像以下的布局怎么实现,作为新手搞了很久,请大牛指点
------解决思路---------------------- 直接相对布局就能搞定了
------解决思路---------------------- 相对布局,线性布局都可以啊
------解决思路---------------------- 布局嵌套一下就行
这是一篇新手教程,是教大家利用CDR制作可爱的毛毛虫,制作方法很简单,很适合新手画。推荐给脚本之家的朋友,喜欢的朋友快过来学习吧!
以上就是CDR制作可爱的毛毛虫,希望大家喜欢!
oracle适合新手学习的存储过程讲解实例
引自:/chinafine/archive//1776102.html
本人新手,求书介绍。 在搞一个项目要研究android手机病毒的传播,想先学会让病毒通过wifi、蓝牙等传播,请大神们介绍有关这方面的书,顺便说说需要掌握哪些知识。
------解决方案-------------------- 这个档次高,要研究底层网络传输了,
新手,关于viewpager的问题 我想用viewpager做一个广告轮播的效果,一开始成功了,是在最外层relativelayout下放了viewpager;后来想要在广告的上方再加一个linearlayout,就出错了,我的做法是在最外层套了一个linearlayout,然后在这个最外层的linearlayout里面,在包裹广告viewpager的relativelyout上面添加想要添加的那
新手求问 我是新手,想自学Android开发,但不知道该从哪入手,希望大神们给点建议,最好是能提供一些视频网站
------解决方案-------------------- Java会不会?不会的话先学学Java
会的话,网上视频多的很,搜一个照着学吧。LINUX c编程 线程应用(一)——创建分离线程和延迟函数 - 开源中国社区
当前访客身份:游客 [
当前位置:
发布于 日 17时,
通过线程设计一个延迟运行的函数,但不挂起当前线程。掌握要点:1.怎么创建一个脱离线程。2.怎么用select()实现一个延迟函数。说明:timeout(int,void&*(*)(void*),void&*);在指定秒数为负数或任何线程执行函数失败时都直接运行指定的延迟执行函数。
代码片段(2)
1.&[代码][C/C++]代码&&&&
#include "unp.h"
/*以下是为使用超时函数定义的接口数据和函数*/
typedef void *(*pthread_f)(void *);
typedef struct toinfo_t{
pthread_f to_
}toinfo_t;
void *timeout_ext(toinfo_t *args){
n=select(1,0,0,0,&args-&tv);
if(n==-1){
if(errno!=EINTR)
print_err("select()");
fprintf(stderr,"select() interrupt by sigint\n");
args-&to_func(args-&to_args);
free(args);
return (void*)0;
int create_detach_thread(pthread_f func,void *args){
pthread_attr_
char buf[BUF_ERR_SIZE];
flag=pthread_attr_init(&attr);
if(flag!=0){
fprintf(stderr,"%s error:%s\n","pthread_attr_init()",geterr_str(flag,buf,sizeof(buf)));
flag=pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
if(flag!=0){
fprintf(stderr,"%s error:%s\n","pthread_attr_setdetachstate()",geterr_str(flag,buf,sizeof(buf)));
goto __err0;
flag=pthread_create(&tid,&attr,(pthread_f)func,args);
if(flag!=0){
fprintf(stderr,"%s error:%s\n","pthread_create()",geterr_str(flag,buf,sizeof(buf)));
flag=pthread_attr_destroy(&attr);
if(flag!=0){
fprintf(stderr,"%s error:%s\n","pthread_attr_destroy()",geterr_str(flag,buf,sizeof(buf)));
void timeout(int secs,pthread_f func,void *args){
pthread_attr_
toinfo_t *
if(secs&0){
toinfo=(toinfo_t*)malloc(sizeof(toinfo_t));
if(toinfo!=NULL){
toinfo-&tv.tv_sec=
toinfo-&to_args=
toinfo-&to_func=
if(create_detach_thread((pthread_f)timeout_ext,toinfo)==0)
free(toinfo);
func(args);
/*以下用户自定义的函数和数据*/
void *user_timeout_func(timeval_t *args){
timeval_t tv,
if(gettimeofday(&tv,0)==-1)
print_err("gettimeofday()");
stv.tv_sec=tv.tv_sec-args-&tv_
if(tv.tv_usec&args-&tv_usec){
stv.tv_usec=tv.tv_usec-args-&tv_
stv.tv_sec-=1;
stv.tv_usec=1*1000000-args-&tv_usec+tv.tv_
fprintf(stderr,"escape time sec %lu ,msec %lu\n",stv.tv_sec,stv.tv_usec);
void intr_handler(int signo){
void *delay(void *args){
for(i=0;i&10000;i++){
for(j=0;i&1000;j++)
return (void*)0;
int main(int argc,char **argv){
if(argc!=2){
fprintf(stderr,"usage %s &seconds&\n",argv[0]);
exit(EXIT_FAILURE);
handle_signal(SIGINT,intr_handler);
if(gettimeofday(&tv,0)==-1)
print_err("gettimeofday()");
timeout(atoi(argv[1]),(pthread_f)user_timeout_func,&tv);
sigemptyset(&sm);
sigaddset(&sm,SIGINT);
sigsuspend(&sm);
2.&[代码]程序的优化&&&&
编译运行以上程序:
gcc -o thread_timeout_func01 thread_timeout_func01.c unp.c -g -w -lpthread
root@U-SERVER:/home/apu/pthread_test# ./thread_timeout_func01 5
^Cselect() interrupt by sigint
^Cselect() interrupt by sigint
escape time sec 5 ,msec 4435
root@U-SERVER:/home/apu/pthread_test#
1.主线程信号屏蔽为只接收SIGQUIT(Ctrl+\)。
2.当我们按下(Ctrl+C)时其他线程会收到了此信号,select()被中断,且select()不会被系统自动重启,而需要我们从用户应用层去重启,重启后select()的timeval_t参数变为剩余时间。所用超时时间的控制是比较精确的(我们也可以从应用层控制,但效果不如select()从内核控制来得好)。
3.但超时结束时,可以看出时间流逝是5秒4435微秒,几乎接近5秒这个单位。
以上测试主程序,是在2个线程的情况下测试的。现在我们来模拟一个繁忙的系统,即CPU的时间片会快速在进程、线程间切换:
void intr_handler(int signo){
fprintf(stderr,"tid %lu caught sigint\n",(unsigned long)pthread_self());
void *delay(void *args){
for(i=0;i&10000;i++){
for(j=0;i&1000;j++)
fprintf(stderr,"one thread end\n");
return (void*)0;
int main(int argc,char **argv){
if(argc!=3){
fprintf(stderr,"usage %s &seconds& &threads&\n",argv[0]);
exit(EXIT_FAILURE);
handle_signal(SIGINT,intr_handler);
for(i=0;i&atoi(argv[2]);i++){
create_detach_thread(delay,0);
if(gettimeofday(&tv,0)==-1)
print_err("gettimeofday()");
fprintf(stderr,"start runing timeout()\n");
timeout(atoi(argv[1]),(pthread_f)user_timeout_func,&tv);
sigemptyset(&sm);
sigaddset(&sm,SIGINT);
sigsuspend(&sm);
创建多个分离线程,每个线程都循环执行一个空语句,已保证CPU在切换。
编译并运行。
root@U-SERVER:/home/apu/pthread_test# gcc -o thread_timeout_func01 thread_timeout_func01.c unp.c -g -w -lpthread
root@U-SERVER:/home/apu/pthread_test# ./thread_timeout_func01 10 10
start runing timeout()
caught sigint
caught sigint
caught sigint
escape time sec 10 ,msec 17164
root@U-SERVER:/home/apu/pthread_test# ./thread_timeout_func01 10 100
start runing timeout()
caught sigint
caught sigint
caught sigint
caught sigint
^Cescape time sec 10 ,msec 141697
caught sigint
root@U-SERVER:/home/apu/pthread_test#
1.信号只会被其中一个线程接收,且是固定的(如果第一个接收信号的线程没有结束)。
2.从select()没有被中断可以看出,优先运行的线程优先接收信号。
3.系统越繁忙,计时越不精准。
修改接口函数timeout(),timeout_ext(),添加时间流逝控制,增加精确度。
void timeout(int secs,pthread_f func,void *args){
pthread_attr_
toinfo_t *
if(secs&0){
if(gettimeofday(&tv,0)==-1)
print_err("gettimeofday()");
tv.tv_sec+=
toinfo=(toinfo_t*)malloc(sizeof(toinfo_t));
if(toinfo!=NULL){
bcopy(&tv,&toinfo-&tv,sizeof(timeval_t));
toinfo-&to_args=
toinfo-&to_func=
if(create_detach_thread((pthread_f)timeout_ext,toinfo)==0)
free(toinfo);
func(args);
void *timeout_ext(toinfo_t *args){
timeval_t tv,
if(gettimeofday(&tv,0)==-1)
print_err("gettimeofday()");
if(args-&tv.tv_sec&tv.tv_sec ||(args-&tv.tv_sec==tv.tv_sec && args-&tv.tv_usec&tv.tv_usec)){
stv.tv_sec=args-&tv.tv_sec-tv.tv_
if(args-&tv.tv_usec&tv.tv_usec){
stv.tv_usec=args-&tv.tv_usec-tv.tv_
stv.tv_sec-=1;
stv.tv_usec=1*1000000+args-&tv.tv_usec-tv.tv_
n=select(1,0,0,0,&stv);
if(n==-1){
if(errno!=EINTR)
print_err("select()");
fprintf(stderr,"select() interrupt by sigint\n");
args-&to_func(args-&to_args);
free(args);
return (void*)0;
编译运行:
root@U-SERVER:/home/apu/pthread_test# ./thread_timeout_func01 10 100
start runing timeout()
caught sigint
caught sigint
caught sigint
caught sigint
^Cescape time sec 10 ,msec 141697
caught sigint
root@U-SERVER:/home/apu/pthread_test# gcc -o thread_timeout_func thread_timeout_func.c unp.c -g -w -lpthread
root@U-SERVER:/home/apu/pthread_test# ./thread_timeout_func 10 100
start runing timeout()
caught sigint
escape time sec 10 ,msec 8565
root@U-SERVER:/home/apu/pthread_test#
从结果来看精准度得到了很大的提高,如果是秒级别的延迟。这点精度丢失不算什么,但如果是毫秒,甚至微妙级别的,这样的精度丢失就尤为严重了。100个线程(进程)运行,误差不到10毫秒,还是非常不错的。
开源中国-程序员在线工具:
相关的代码(285)
开源从代码分享开始
凯子哥的其它代码博客访问: 517420
博文数量: 394
博客积分: 11
博客等级: 民兵
技术积分: 2355
注册时间:
真正的安全感,来自你对自己的信心,是你每个阶段性目标的实现,而真正的归属感,在于你的内心深处,对自己命运的把控,因为你最大的对手永远都是自己。|
Network+Security+Database.
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
分类: LINUX
级别: 初级
()国防科技大学计算机学院
2003 年 5 月 19 日
自从多线程编程的概念出现在 Linux 中以来,Linux 多线应用的发展总是与两个问题脱不开干系:兼容性、效率。本文从线程模型入手,通过分析目前 Linux 平台上最流行的 LinuxThreads 线程库的实现及其不足,描述了 Linux 社区是如何看待和解决兼容性和效率这两个问题的。
按照教科书上的定义,进程是资源管理的最小单位,线程是程序执行的最小单位。在操作系统设计上,从进程演化出线程,最主要的目的就是更好的支持SMP以及减小(进程/线程)上下文切换开销。
无论按照怎样的分法,一个进程至少需要一个线程作为它的指令执行体,进程管理着资源(比如cpu、内存、文件等等),而将线程分配到某个cpu上执行。一个进程当然可以拥有多个线程,此时,如果进程运行在SMP机器上,它就可以同时使用多个cpu来执行各个线程,达到最大程度的并行,以提高效率;同时,即使是在单cpu的机器上,采用多线程模型来设计程序,正如当年采用多进程模型代替单进程模型一样,使设计更简洁、功能更完备,程序的执行效率也更高,例如采用多个线程响应多个输入,而此时多线程模型所实现的功能实际上也可以用多进程模型来实现,而与后者相比,线程的上下文切换开销就比进程要小多了,从语义上来说,同时响应多个输入这样的功能,实际上就是共享了除cpu以外的所有资源的。
针对线程模型的两大意义,分别开发出了核心级线程和用户级线程两种线程模型,分类的标准主要是线程的调度者在核内还是在核外。前者更利于并发使用多处理器的资源,而后者则更多考虑的是上下文切换开销。在目前的商用系统中,通常都将两者结合起来使用,既提供核心线程以满足smp系统的需要,也支持用线程库的方式在用户态实现另一套线程机制,此时一个核心线程同时成为多个用户态线程的调度者。正如很多技术一样,"混合"通常都能带来更高的效率,但同时也带来更大的实现难度,出于"简单"的设计思路,Linux从一开始就没有实现混合模型的计划,但它在实现上采用了另一种思路的"混合"。
在线程机制的具体实现上,可以在操作系统内核上实现线程,也可以在核外实现,后者显然要求核内至少实现了进程,而前者则一般要求在核内同时也支持进程。核心级线程模型显然要求前者的支持,而用户级线程模型则不一定基于后者实现。这种差异,正如前所述,是两种分类方式的标准不同带来的。
当核内既支持进程也支持线程时,就可以实现线程-进程的"多对多"模型,即一个进程的某个线程由核内调度,而同时它也可以作为用户级线程池的调度者,选择合适的用户级线程在其空间中运行。这就是前面提到的"混合"线程模型,既可满足多处理机系统的需要,也可以最大限度的减小调度开销。绝大多数商业操作系统(如Digital Unix、Solaris、Irix)都采用的这种能够完全实现POSIX1003.1c标准的线程模型。在核外实现的线程又可以分为"一对一"、"多对一"两种模型,前者用一个核心进程(也许是轻量进程)对应一个线程,将线程调度等同于进程调度,交给核心完成,而后者则完全在核外实现多线程,调度也在用户态完成。后者就是前面提到的单纯的用户级线程模型的实现方式,显然,这种核外的线程调度器实际上只需要完成线程运行栈的切换,调度开销非常小,但同时因为核心信号(无论是同步的还是异步的)都是以进程为单位的,因而无法定位到线程,所以这种实现方式不能用于多处理器系统,而这个需求正变得越来越大,因此,在现实中,纯用户级线程的实现,除算法研究目的以外,几乎已经消失了。
Linux内核只提供了轻量进程的支持,限制了更高效的线程模型的实现,但Linux着重优化了进程的调度开销,一定程度上也弥补了这一缺陷。目前最流行的线程机制LinuxThreads所采用的就是线程-进程"一对一"模型,调度交给核心,而在用户级实现一个包括信号处理在内的线程管理机制。Linux-LinuxThreads的运行机制正是本文的描述重点。
最初的进程定义都包含程序、资源及其执行三部分,其中程序通常指代码,资源在操作系统层面上通常包括内存资源、IO资源、信号处理等部分,而程序的执行通常理解为执行上下文,包括对cpu的占用,后来发展为线程。在线程概念出现以前,为了减小进程切换的开销,操作系统设计者逐渐修正进程的概念,逐渐允许将进程所占有的资源从其主体剥离出来,允许某些进程共享一部分资源,例如文件、信号,数据内存,甚至代码,这就发展出轻量进程的概念。Linux内核在2.0.x版本就已经实现了轻量进程,应用程序可以通过一个统一的clone()系统调用接口,用不同的参数指定创建轻量进程还是普通进程。在内核中,clone()调用经过参数传递和解释后会调用do_fork(),这个核内函数同时也是fork()、vfork()系统调用的最终实现:
int do_fork(unsigned long clone_flags, unsigned long stack_start,
struct pt_regs *regs, unsigned long stack_size)
其中的clone_flags取自以下宏的"或"值:
#define CSIGNAL
0x000000ff /* signal mask to be sent at exit */
#define CLONE_VM
0x /* set if VM shared between processes */
#define CLONE_FS
0x /* set if fs info shared between processes */
#define CLONE_FILES
0x /* set if open files shared between processes */
#define CLONE_SIGHAND 0x /* set if signal handlers and blocked signals shared */
#define CLONE_PID
0x /* set if pid shared */
#define CLONE_PTRACE 0x /* set if we want to let tracing continue on the child too */
#define CLONE_VFORK 0x /* set if the parent wants the child to wake it up on mm_release */
#define CLONE_PARENT 0x /* set if we want to have the same parent as the cloner */
#define CLONE_THREAD 0x /* Same thread group? */
#define CLONE_NEWNS 0x /* New namespace group? */
#define CLONE_SIGNAL
(CLONE_SIGHAND | CLONE_THREAD)
在do_fork()中,不同的clone_flags将导致不同的行为,对于LinuxThreads,它使用(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND)参数来调用clone()创建"线程",表示共享内存、共享文件系统访问计数、共享文件描述符表,以及共享信号处理方式。本节就针对这几个参数,看看Linux内核是如何实现这些资源的共享的。
1.CLONE_VM
do_fork()需要调用copy_mm()来设置task_struct中的mm和active_mm项,这两个mm_struct数据与进程所关联的内存空间相对应。如果do_fork()时指定了CLONE_VM开关,copy_mm()将把新的task_struct中的mm和active_mm设置成与current的相同,同时提高该mm_struct的使用者数目(mm_struct::mm_users)。也就是说,轻量级进程与父进程共享内存地址空间,由下图示意可以看出mm_struct在进程中的地位:
2.CLONE_FS
task_struct中利用fs(struct fs_struct *)记录了进程所在文件系统的根目录和当前目录信息,do_fork()时调用copy_fs()复制了这个结构;而对于轻量级进程则仅增加fs->count计数,与父进程共享相同的fs_struct。也就是说,轻量级进程没有独立的文件系统相关的信息,进程中任何一个线程改变当前目录、根目录等信息都将直接影响到其他线程。
3.CLONE_FILES
一个进程可能打开了一些文件,在进程结构task_struct中利用files(struct files_struct *)来保存进程打开的文件结构(struct file)信息,do_fork()中调用了copy_files()来处理这个进程属性;轻量级进程与父进程是共享该结构的,copy_files()时仅增加files->count计数。这一共享使得任何线程都能访问进程所维护的打开文件,对它们的操作会直接反映到进程中的其他线程。
4.CLONE_SIGHAND
每一个Linux进程都可以自行定义对信号的处理方式,在task_struct中的sig(struct signal_struct)中使用一个struct k_sigaction结构的数组来保存这个配置信息,do_fork()中的copy_sighand()负责复制该信息;轻量级进程不进行复制,而仅仅增加signal_struct::count计数,与父进程共享该结构。也就是说,子进程与父进程的信号处理方式完全相同,而且可以相互更改。
do_fork()中所做的工作很多,在此不详细描述。对于SMP系统,所有的进程fork出来后,都被分配到与父进程相同的cpu上,一直到该进程被调度时才会进行cpu选择。
尽管Linux支持轻量级进程,但并不能说它就支持核心级线程,因为Linux的"线程"和"进程"实际上处于一个调度层次,共享一个进程标识符空间,这种限制使得不可能在Linux上实现完全意义上的POSIX线程机制,因此众多的Linux线程库实现尝试都只能尽可能实现POSIX的绝大部分语义,并在功能上尽可能逼近。
LinuxThreads是目前Linux平台上使用最为广泛的线程库,由Xavier Leroy (Xavier.Leroy@inria.fr)负责开发完成,并已绑定在GLIBC中发行。它所实现的就是基于核心轻量级进程的"一对一"线程模型,一个线程实体对应一个核心轻量级进程,而线程之间的管理在核外函数库中实现。
LinuxThreads定义了一个struct _pthread_descr_struct数据结构来描述线程,并使用全局数组变量__pthread_handles来描述和引用进程所辖线程。在__pthread_handles中的前两项,LinuxThreads定义了两个全局的系统线程:__pthread_initial_thread和__pthread_manager_thread,并用__pthread_main_thread表征__pthread_manager_thread的父线程(初始为__pthread_initial_thread)。
struct _pthread_descr_struct是一个双环链表结构,__pthread_manager_thread所在的链表仅包括它一个元素,实际上,__pthread_manager_thread是一个特殊线程,LinuxThreads仅使用了其中的errno、p_pid、p_priority等三个域。而__pthread_main_thread所在的链则将进程中所有用户线程串在了一起。经过一系列pthread_create()之后形成的__pthread_handles数组将如下图所示:
新创建的线程将首先在__pthread_handles数组中占据一项,然后通过数据结构中的链指针连入以__pthread_main_thread为首指针的链表中。这个链表的使用在介绍线程的创建和释放的时候将提到。
LinuxThreads遵循POSIX1003.1c标准,其中对线程库的实现进行了一些范围限制,比如进程最大线程数,线程私有数据区大小等等。在LinuxThreads的实现中,基本遵循这些限制,但也进行了一定的改动,改动的趋势是放松或者说扩大这些限制,使编程更加方便。这些限定宏主要集中在sysdeps/unix/sysv/linux/bits/local_lim.h(不同平台使用的文件位置不同)中,包括如下几个:
每进程的私有数据key数,POSIX定义_POSIX_THREAD_KEYS_MAX为128,LinuxThreads使用PTHREAD_KEYS_MAX,1024;私有数据释放时允许执行的操作数,LinuxThreads与POSIX一致,定义PTHREAD_DESTRUCTOR_ITERATIONS为4;每进程的线程数,POSIX定义为64,LinuxThreads增大到1024(PTHREAD_THREADS_MAX);线程运行栈最小空间大小,POSIX未指定,LinuxThreads使用PTHREAD_STACK_MIN,16384(字节)。
"一对一"模型的好处之一是线程的调度由核心完成了,而其他诸如线程取消、线程间的同步等工作,都是在核外线程库中完成的。在LinuxThreads中,专门为每一个进程构造了一个管理线程,负责处理线程相关的管理工作。当进程第一次调用pthread_create()创建一个线程的时候就会创建(__clone())并启动管理线程。
在一个进程空间内,管理线程与其他线程之间通过一对"管理管道(manager_pipe[2])"来通讯,该管道在创建管理线程之前创建,在成功启动了管理线程之后,管理管道的读端和写端分别赋给两个全局变量__pthread_manager_reader和__pthread_manager_request,之后,每个用户线程都通过__pthread_manager_request向管理线程发请求,但管理线程本身并没有直接使用__pthread_manager_reader,管道的读端(manager_pipe[0])是作为__clone()的参数之一传给管理线程的,管理线程的工作主要就是监听管道读端,并对从中取出的请求作出反应。
创建管理线程的流程如下所示: (全局变量pthread_manager_request初值为-1)
初始化结束后,在__pthread_manager_thread中记录了轻量级进程号以及核外分配和管理的线程id,2*PTHREAD_THREADS_MAX+1这个数值不会与任何常规用户线程id冲突。管理线程作为pthread_create()的调用者线程的子线程运行,而pthread_create()所创建的那个用户线程则是由管理线程来调用clone()创建,因此实际上是管理线程的子线程。(此处子线程的概念应该当作子进程来理解。)
__pthread_manager()就是管理线程的主循环所在,在进行一系列初始化工作后,进入while(1)循环。在循环中,线程以2秒为timeout查询(__poll())管理管道的读端。在处理请求前,检查其父线程(也就是创建manager的主线程)是否已退出,如果已退出就退出整个进程。如果有退出的子线程需要清理,则调用pthread_reap_children()清理。
然后才是读取管道中的请求,根据请求类型执行相应操作(switch-case)。具体的请求处理,源码中比较清楚,这里就不赘述了。
在LinuxThreads中,管理线程的栈和用户线程的栈是分离的,管理线程在进程堆中通过malloc()分配一个THREAD_MANAGER_STACK_SIZE字节的区域作为自己的运行栈。
用户线程的栈分配办法随着体系结构的不同而不同,主要根据两个宏定义来区分,一个是NEED_SEPARATE_REGISTER_STACK,这个属性仅在IA64平台上使用;另一个是FLOATING_STACK宏,在i386等少数平台上使用,此时用户线程栈由系统决定具体位置并提供保护。与此同时,用户还可以通过线程属性结构来指定使用用户自定义的栈。因篇幅所限,这里只能分析i386平台所使用的两种栈组织方式:FLOATING_STACK方式和用户自定义方式。
在FLOATING_STACK方式下,LinuxThreads利用mmap()从内核空间中分配8MB空间(i386系统缺省的最大栈空间大小,如果有运行限制(rlimit),则按照运行限制设置),使用mprotect()设置其中第一页为非访问区。该8M空间的功能分配如下图:
低地址被保护的页面用来监测栈溢出。
对于用户指定的栈,在按照指针对界后,设置线程栈顶,并计算出栈底,不做保护,正确性由用户自己保证。
不论哪种组织方式,线程描述结构总是位于栈顶紧邻堆栈的位置。
每个LinuxThreads线程都同时具有线程id和进程id,其中进程id就是内核所维护的进程号,而线程id则由LinuxThreads分配和维护。
__pthread_initial_thread的线程id为PTHREAD_THREADS_MAX,__pthread_manager_thread的是2*PTHREAD_THREADS_MAX+1,第一个用户线程的线程id为PTHREAD_THREADS_MAX+2,此后第n个用户线程的线程id遵循以下公式:
tid=n*PTHREAD_THREADS_MAX+n+1
这种分配方式保证了进程中所有的线程(包括已经退出)都不会有相同的线程id,而线程id的类型pthread_t定义为无符号长整型(unsigned long int),也保证了有理由的运行时间内线程id不会重复。
从线程id查找线程数据结构是在pthread_handle()函数中完成的,实际上只是将线程号按PTHREAD_THREADS_MAX取模,得到的就是该线程在__pthread_handles中的索引。
在pthread_create()向管理线程发送REQ_CREATE请求之后,管理线程即调用pthread_handle_create()创建新线程。分配栈、设置thread属性后,以pthread_start_thread()为函数入口调用__clone()创建并启动新线程。pthread_start_thread()读取自身的进程id号存入线程描述结构中,并根据其中记录的调度方法配置调度。一切准备就绪后,再调用真正的线程执行函数,并在此函数返回后调用pthread_exit()清理现场。
由于Linux内核的限制以及实现难度等等原因,LinuxThreads并不是完全POSIX兼容的,在它的发行README中有说明。
1)进程id问题
这个不足是最关键的不足,引起的原因牵涉到LinuxThreads的"一对一"模型。
Linux内核并不支持真正意义上的线程,LinuxThreads是用与普通进程具有同样内核调度视图的轻量级进程来实现线程支持的。这些轻量级进程拥有独立的进程id,在进程调度、信号处理、IO等方面享有与普通进程一样的能力。在源码阅读者看来,就是Linux内核的clone()没有实现对CLONE_PID参数的支持。
在内核do_fork()中对CLONE_PID的处理是这样的:
if (clone_flags & CLONE_PID) {
if (current->pid)
goto fork_
这段代码表明,目前的Linux内核仅在pid为0的时候认可CLONE_PID参数,实际上,仅在SMP初始化,手工创建进程的时候才会使用CLONE_PID参数。
按照POSIX定义,同一进程的所有线程应该共享一个进程id和父进程id,这在目前的"一对一"模型下是无法实现的。
2)信号处理问题
由于异步信号是内核以进程为单位分发的,而LinuxThreads的每个线程对内核来说都是一个进程,且没有实现"线程组",因此,某些语义不符合POSIX标准,比如没有实现向进程中所有线程发送信号,README对此作了说明。
如果核心不提供实时信号,LinuxThreads将使用SIGUSR1和SIGUSR2作为内部使用的restart和cancel信号,这样应用程序就不能使用这两个原本为用户保留的信号了。在Linux kernel 2.1.60以后的版本都支持扩展的实时信号(从_SIGRTMIN到_SIGRTMAX),因此不存在这个问题。
某些信号的缺省动作难以在现行体系上实现,比如SIGSTOP和SIGCONT,LinuxThreads只能将一个线程挂起,而无法挂起整个进程。
3)线程总数问题
LinuxThreads将每个进程的线程最大数目定义为1024,但实际上这个数值还受到整个系统的总进程数限制,这又是由于线程其实是核心进程。
在kernel 2.4.x中,采用一套全新的总进程数计算方法,使得总进程数基本上仅受限于物理内存的大小,计算公式在kernel/fork.c的fork_init()函数中:
max_threads = mempages / (THREAD_SIZE/PAGE_SIZE) / 8
在i386上,THREAD_SIZE=2*PAGE_SIZE,PAGE_SIZE=2^12(4KB),mempages=物理内存大小/PAGE_SIZE,对于256M的内存的机器,mempages=256*2^20/2^12=256*2^8,此时最大线程数为4096。
但为了保证每个用户(除了root)的进程总数不至于占用一半以上物理内存,fork_init()中继续指定:
init_task.rlim[RLIMIT_NPROC].rlim_cur = max_threads/2;
init_task.rlim[RLIMIT_NPROC].rlim_max = max_threads/2;
这些进程数目的检查都在do_fork()中进行,因此,对于LinuxThreads来说,线程总数同时受这三个因素的限制。
4)管理线程问题
管理线程容易成为瓶颈,这是这种结构的通病;同时,管理线程又负责用户线程的清理工作,因此,尽管管理线程已经屏蔽了大部分的信号,但一旦管理线程死亡,用户线程就不得不手工清理了,而且用户线程并不知道管理线程的状态,之后的线程创建等请求将无人处理。
5)同步问题
LinuxThreads中的线程同步很大程度上是建立在信号基础上的,这种通过内核复杂的信号处理机制的同步方式,效率一直是个问题。
6)其他POSIX兼容性问题
Linux中很多系统调用,按照语义都是与进程相关的,比如nice、setuid、setrlimit等,在目前的LinuxThreads中,这些调用都仅仅影响调用者线程。
7)实时性问题
线程的引入有一定的实时性考虑,但LinuxThreads暂时不支持,比如调度选项,目前还没有实现。不仅LinuxThreads如此,标准的Linux在实时性上考虑都很少。
LinuxThreads的问题,特别是兼容性上的问题,严重阻碍了Linux上的跨平台应用(如Apache)采用多线程设计,从而使得Linux上的线程应用一直保持在比较低的水平。在Linux社区中,已经有很多人在为改进线程性能而努力,其中既包括用户级线程库,也包括核心级和用户级配合改进的线程库。目前最为人看好的有两个项目,一个是RedHat公司牵头研发的NPTL(Native Posix Thread Library),另一个则是IBM投资开发的NGPT(Next Generation Posix Threading),二者都是围绕完全兼容POSIX 1003.1c,同时在核内和核外做工作以而实现多对多线程模型。这两种模型都在一定程度上弥补了LinuxThreads的缺点,且都是重起炉灶全新设计的。
NPTL的设计目标归纳可归纳为以下几点:
POSIX兼容性
SMP结构的利用
低启动开销
低链接开销(即不使用线程的程序不应当受线程库的影响)
与LinuxThreads应用的二进制兼容性
软硬件的可扩展能力
多体系结构支持
在技术实现上,NPTL仍然采用1:1的线程模型,并配合glibc和最新的Linux Kernel2.5.x开发版在信号处理、线程同步、存储管理等多方面进行了优化。和LinuxThreads不同,NPTL没有使用管理线程,核心线程的管理直接放在核内进行,这也带了性能的优化。
主要是因为核心的问题,NPTL仍然不是100%POSIX兼容的,但就性能而言相对LinuxThreads已经有很大程度上的改进了。
IBM的开放源码项目NGPT在日推出了稳定的2.2.0版,但相关的文档工作还差很多。就目前所知,NGPT是基于GNU Pth(GNU Portable Threads)项目而实现的M:N模型,而GNU Pth是一个经典的用户级线程库实现。
按照2003年3月NGPT官方网站上的通知,NGPT考虑到NPTL日益广泛地为人所接受,为避免不同的线程库版本引起的混乱,今后将不再进行进一步开发,而今进行支持性的维护工作。也就是说,NGPT已经放弃与NPTL竞争下一代Linux POSIX线程库标准。
3.其他高效线程机制
此处不能不提到Scheduler Activations。这个1991年在ACM上发表的多线程内核结构影响了很多多线程内核的设计,其中包括Mach3.0、NetBSD和商业版本Digital Unix(现在叫Compaq True64 Unix)。它的实质是在使用用户级线程调度的同时,尽可能地减少用户级对核心的系统调用请求,而后者往往是运行开销的重要来源。采用这种结构的线程机制,实际上是结合了用户级线程的灵活高效和核心级线程的实用性,因此,包括Linux、FreeBSD在内的多个开放源码操作系统设计社区都在进行相关研究,力图在本系统中实现Scheduler Activations。
[Linus Torvalds,2002] Linux内核源码v2.4.20
[GNU,2002] Glibc源码v2.2.2(内含LinuxThreads v0.9)
[Thomas E. Terrill,1997] An Introduction to Threads Using The LinuxThreads Interface
[Ulrich Drepper,Ingo Molnar,2003] The Native POSIX Thread Library for Linux
,NGPT官方网站
[Ralf S. Engelschall,2000] Portable Multithreading
[Thomas E. Anderson, Brian N. Bershad, Edward D. Lazowska, Henry M. Levy,1992] Scheduler Activations: Effective Kernel Support for the User-Level Management of Parallelism
[] Linux线程初探
杨沙洲,目前在国防科技大学计算机学院攻读软件方向博士学位。
阅读(207) | 评论(0) | 转发(0) |
相关热门文章
给主人留下些什么吧!~~
请登录后评论。

我要回帖

更多关于 java 多线程 的文章

 

随机推荐