线程Linux系统下的多线程编程入门

linux多线程编程  时间:2021-01-19  阅读:()

Linux系统下的多线程编程入门

2006-01-1709:13作者 pcstudy出处 blogchina 责任编辑方舟

相关专题 Linux设备驱动程序开发入门

引言

线程th read技术早在60年代就被提出但真正应用多线程到操作系统中去是在80年代中期s olaris是这方面的佼佼者。传统的U n ix也支持线程的概念但是在一个进程process中只允许有一个线程这样多线程就意味着多进程。现在多线程技术已经被许多操作系统所支持包括Windows/NT当然也包括Linux。

为什么有了进程的概念后还要再引入线程呢使用多线程到底有哪些好处什么的系统应该选用多线程我们首先必须回答这些问题。

使用多线程的理由之一是和进程相比它是一种非常"节俭"的多任务操作方式。我们知道在Linux 系统下启动一个新的进程必须分配给它独立的地址空间建立众多的数据表来维护它的代码段、堆栈段和数据段这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程它们彼此之间使用相同的地址空间共享大部分数据启动一个线程所花费的空间远远小于启动一个进程所花费的空间而且线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计 总的说来 桓鼋 痰目 笤际且桓鱿叱炭 ?0倍左右 当然在具体的系统上这个数据可能会有较大的区别。

使用多线程的理由之二是线程间方便的通信机制。对不同进程来说它们具有独立的数据空间要进行数据的传递只能通过通信的方式进行这种方式不仅费时而且很不方便。线程则不然 由于同一进程下的线程之间共享数据空间所以一个线程的数据可以直接为其它线程所用这不仅快捷而且方便。当然数据的共享也带来其他一些问题有的变量不能同时被两个线程所修改有的子程序中声明为s tati c 的数据更有可能给多线程程序带来灾难性的打击这些正是编写多线程程序时最需要注意的地方。

除了以上所说的优点外不和进程比较多线程程序作为一种多任务、并发的工作方式 当然有以下的优点

1 )提高应用程序响应。这对图形界面的程序尤其有意义当一个操作耗时很长时整个系统都会等待这个操作此时程序不会响应键盘、 鼠标、菜单的操作而使用多线程技术将耗时长的操作tim e consuming置于一个新的线程可以避免这种尴尬的情况。

2)使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时不同的线程运行于不同的CPU上。

3)改善程序结构。一个既长又复杂的进程可以考虑分为多个线程成为几个独立或半独立的运行部分这样的程序会利于理解和修改。

下面我们先来尝试编写一个简单的多线程程序。

简单的多线程编程

Linux系统下的多线程遵循POSIX线程接口称为pthread。编写Linux下的多线程程序需要使用头文件pthread h连接时需要使用库l ibpthread a。顺便说一下 Linux下pthread的实现是通过系统调用clone 来实现的。 clone  是Linux所特有的系统调用它的使用方式类似fork关于clone  的详细情况有兴趣的读者可以去查看有关文档说明。下面我们展示一个最简单的多线程程序example1 c。

我们编译此程序gcc example1 c-lpthread-o example1

运行example1 我们得到如下结果

This is a pthread

再次运行我们可能得到如下结果

前后两次结果不一样这是两个线程争夺CPU资源的结果。上面的示例中我们使用到了两个函数pthread_create和pthread_join并声明了一个pthread_t型的变量。pthread_t在头文件/usr/include/bits/pthreadtypes h中定义typedef unsigned long intpthread_t;

它是一个线程的标识符。函数pth re a d_cre ate用来创建一个线程它的原型为extern int pthread_create__P((pthread_t*__thread,__const pthread_attr_t*__attr,void

*(*__s ta rt_ro u ti ne) (vo i d*),vo i d*__a rg));

第一个参数为指向线程标识符的指针第二个参数用来设置线程属性第三个参数是线程运行函数的起始地址最后一个参数是运行函数的参数。这里我们的函数th read不需要参数所以最后一个参数设为空指针。第二个参数我们也设为空指针这样将生成默认属性的线程。对线程属性的设定和修改我们将在下一节阐述。当创建线程成功时 函数返回0若不为0则说明创建线程失败常见的错误返回代码为EAGAIN和EINVAL。前者表示系统限制创建新的线程例如线程数目过多了后者表示第二个参数代表的线程属性值非法。创建线程成功后新创建的线程则运行参数三和参数四确定的函数原来的线程则继续运行下一行代码。

函数pthread_join用来等待一个线程的结束。函数原型为extern int pthread_join__P((pthread_t__th,void**__thread_return));

第一个参数为被等待的线程标识符第二个参数为一个用户定义的指针它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数调用它的函数将一直等待到被等待的线程结束为止 当函数返回时被等待线程的资源被收回。一个线程的结束有两种途径一种是象我们上面的例子一样 函数结束了调用它的线程也就结束了 另一种方式是通过函数pthread_exit来实现。它的函数原型为extern void pthread_exit__P((void*__retval))__attribute__((__noreturn__));

唯一的参数是函数的返回代码只要pth read_join中的第二个参数th read_retu rn不是N ULL这个值将被传递给thread_return。最后要说明的是一个线程不能被多个线程等待否则第一个接收到信号的线

程成功返回其余调用pthread_join的线程则返回错误代码ESRCH。

在这一节里我们编写了一个最简单的线程并掌握了最常用的三个函数pth read_createpth read_joi n 和pthread_exit。下面我们来了解线程的一些常用属性以及如何设置这些属性。

修改线程的属性

在上一节的例子里我们用pth re ad_cre ate函数创建了一个线程在这个线程中我们使用了默认参数 即将该函数的第二个参数设为NULL。的确对大多数程序来说使用默认属性就够了但我们还是有必要来了解一下线程的有关属性。

属性结构为pthread_attr_t它同样在头文件/usr/include/pthread h中定义喜欢追根问底的人可以自己去查看。属性值不能直接设置须使用相关函数进行操作初始化的函数为pthread_attr_init这个函数必须在pth re a d_cre ate函数之前调用。属性对象主要包括是否绑定、是否分离、堆栈地址、堆栈大小、优先级。默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。

关于线程的绑定牵涉到另外一个概念轻进程LWP LightWeight Process 。轻进程可以理解为内核线程它位于用户层和系统层之间。系统对线程资源的分配、对线程的控制是通过轻进程来实现的一个轻进程可以控制一个或多个线程。默认状况下启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制的这种状况即称为非绑定的。绑定状况下则顾名思义 即某个线程固定的"绑"在一个轻进程之上。被绑定的线程具有较高的响应速度这是因为CPU时间片的调度是面向轻进程的绑定的线程可以保证在需要的时候它总有一个轻进程可用。通过设置被绑定的轻进程的优先级和调度级可以使得绑定的线程满足诸如实时反应之类的要求。

设置线程绑定状态的函数为pthread_attr_setscope它有两个参数第一个是指向属性结构的指针第二个是绑定类型它有两个取值 PTHREAD_SCOPE_SYSTEM绑定的和

PTHREAD_SCOPE_PROCESS 非绑定的 。下面的代码即创建了一个绑定的线程。

线程的分离状态决定一个线程以什么样的方式来终止自己。在上面的例子中我们采用了线程的默认属性 即为非分离状态这种情况下原有的线程等待创建的线程结束。只有当pthread_join  函数返回时创建的线程才算终止才能释放自己占用的系统资源。而分离线程不是这样子的它没有被其他的线程所等待 自己运行结束了线程也就终止了马上释放系统资源。程序员应该根据自己的需要选择适当的分离状态。设置线程分离状态的函数为pth read_attr_setd eta chs tate pth read_attr_t*attr, i n t detachstate 。第二个参数可选为PTHREAD_CREATE_DETACHED 分离线程和PTHREAD

_CREATE_JOINABLE 非分离线程 。这里要注意的一点是如果设置一个线程为分离线程而这个线程运行又非常快它很可能在pth re ad_create函数返回之前就终止了它终止以后就可能将线程号和系统资源移交给其他的线程使用这样调用pth re ad_create的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施最简单的方法之一是可以在被创建的线程里调用pthread_cond_timewait函数让这个线程等待一会儿 留出足够的时间让函数pth rea d_cre ate返回。设置一段等待时间是在多线程编程里常用的方法。但是注意不要使用诸如wait  之类的函数它们是使整个进程睡眠并不能解决线程同步的问题。

另外一个可能常用的属性是线程的优先级它存放在结构s ched_param中。用函数pthread_attr_getschedparam和函数pthread_attr_setschedparam进行存放一般说来我们总是先取优先级对取得的值修改后再存放回去。下面即是一段简单的例子。

线程的数据处理

和进程相比线程的最大优点之一是数据的共享性各个进程共享父进程处沿袭的数据段可以方便的获得、修改数据。但这也给多线程编程带来了许多问题。我们必须当心有多个不同的进程访问相同的变量。许多函数是不可重入的 即同时不能运行一个函数的多个拷贝除非使用不同的数据段 。在函数中声明的静态变量常常带来问题 函数的返回值也会有问题。因为如果返回的是函数内部静态声明的空间的地址则在一个线程调用该函数得到地址后使用该地址指向的数据时别的线程可能调用此函数并修改了这一段数据。在进程中共享的变量必须用关键字volati le来定义这是为了防止编译器在优化时如gcc 中使用-OX参数改变它们的使用方式。为了保护变量我们必须使用信号量、互斥等方法来保证我们对变量的正确使用。下面我们就逐步介绍处理线程数据时的有关知识。

1 、线程数据

在单线程的程序里有两种基本的数据全局变量和局部变量。但在多线程程序里还有第三种数据类型线程数据TSD:Th read-Specific Data 。它和全局变量很象在线程内部各个函数可以象使用全局变量一样调用它但它对线程外部的其它线程是不可见的。这种数据的必要性是显而易见的。例如我们常见的变量errno它返回标准的出错信息。它显然不能是一个局部变量几乎每个函数都应该可以调用它但它又不能是一个全局变量否则在A线程里输出的很可能是B线程的出错信息。要实现诸如此类的变量我们就必须使用线程数据。我们为每个线程数据创建一个键它和这个键相关联在各个线程里

都使用这个键来指代线程数据但在不同的线程里这个键代表的数据是不同的在同一个线程里它代表同样的数据内容。

和线程数据相关的函数主要有4个创建一个键为一个键指定线程数据从一个键读取线程数据删除键。

创建键的函数原型为extern int pthread_key_create__P((pthread_key_t*__key,void(*__destr_function) (void*)));

第一个参数为指向一个键值的指针第二个参数指明了一个d es tru cto r函数如果这个参数不为空那么当每个线程结束时系统将调用这个函数来释放绑定在这个键上的内存块。这个函数常和函数pthread_once((pthread_once_t*once_control ,void(*initroutine) (void)))一起使用为了让这个键只被创建一次。函数pthread_once声明一个初始化函数第一次调用pthread_once时它执行这个函数 以后的调用将被它忽略。

在下面的例子中我们创建一个键并将它和某个数据相关联。我们要定义一个函数createWindow这个函数定义一个图形窗口 数据类型为Fl_Window*这是图形界面开发工具FLTK中的数据类型 。由于各个线程都会调用这个函数所以我们使用线程数据。

}

这样在不同的线程中调用函数cre ate MyWi n都可以得到在线程内部均可见的窗口变量这个变量通过函数pthread_gets pecific得到。在上面的例子中我们已经使用了函数pthread_sets pecific来将线程数据和一个键绑定在一起。这两个函数的原型如下extern int pthread_setspecific__P((pthread_key_t__key,__const void*__pointer));extern void*pthread_gets pecific__P((pthread_key_t__key));

这两个函数的参数意义和使用方法是显而易见的。要注意的是用pth read_setspecific为一个键指定新的线程数据时必须自己释放原有的线程数据以回收空间。这个过程函数pth read_key_delete用来删除一个键这个键占用的内存将被释放但同样要注意的是它只释放键占用的内存并不释放该键关联的线程数据所占用的内存资源而且它也不会触发函数pth read_key_create中定义的d es tru cto r函数。线程数据的释放必须在释放键之前完成。

2、互斥锁

互斥锁用来保证一段时间内只有一个线程在执行一段代码。必要性显而易见假设各个线程向同一个文件顺序写入数据最后得到的结果一定是灾难性的。

我们先看下面一段代码。这是一个读/写程序它们公用一个缓冲区并且我们假定一个缓冲区只能保存一条信息。即缓冲区只有两个状态有信息或没有信息。

这里声明了互斥锁变量m utex结构pthread_m utex_t为不公开的数据类型其中包含一个系统分配的属性对象。函数pthread_mutex_init用来生成一个互斥锁。 NULL参数表明使用默认属性。如果需要声明特定属性的互斥锁须调用函数pthread_m utexattr_init。函数pthread_m utexattr_setps hared和函数pthread_m utexattr_settype用来设置互斥锁属性。前一个函数设置属性pshared它有两个取值PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用来不同进程中的线程同步后者用于同步本进程的不同线程。在上面的例子中我们使用的是默认属性PTHREAD_PROCESS_PRIVATE。后者用来设置互斥锁类型可选的类型有PTHREAD_MUTEX_NORMAL、

PTHREAD_MUTEX_ERRORCHECK、 PTHREAD_MUTEX_RECURSIVE和PTHREAD

_MUTEX_DEFAULT。它们分别定义了不同的上所、解锁机制一般情况下选用最后一个默认属性。pthread_m utex_lock声明开始用互斥锁上锁此后的代码直至调用pthread_mutex_unlock为止均被上锁即同一时间只能被一个线程调用执行。当一个线程执行到pthread_mutex_lock处时如果该锁此时被另一个线程使用那此线程被阻塞 即程序将等待到另一个线程释放此互斥锁。在上面的例子中我们使用了pthread_delay_np函数让线程睡眠一段时间就是为了防止一个线程始终占据此函数。

上面的例子非常简单就不再介绍了需要提出的是在使用互斥锁的过程中很有可能会出现死锁两个线程试图同时占用两个资源并按不同的次序锁定相应的互斥锁例如两个线程都需要锁定互斥锁1和互斥锁2 a线程先锁定互斥锁1  b线程先锁定互斥锁2这时就出现了死锁。此时我们可以使用函数pthread_m utex_trylock它是函数pthread_m utex_lock的非阻塞版本当它发现死锁不可避免时它会返回相应的信息程序员可以针对死锁做出相应的处理。另外不同的互斥锁类型对死锁的处理不一样但最主要的还是要程序员自己在程序设计注意这一点。

3、条件变量

前一节中我们讲述了如何使用互斥锁来实现线程间数据的共享和通信互斥锁一个明显的缺点是它只有两种状态锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足它常和互斥锁一起使用。使用时条件变量被用来阻塞一个线程 当条件不满足时线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来条件变量被用来进行线承间的同步。

条件变量的结构为pth read_cond_t函数pth read_cond_in it  被用来初始化一个条件变量。它的原型为extern int pthread_cond_init__P((pthread_cond_t*__cond,__const pthread_condattr_t

*__cond_attr));

其中cond是一个指向结构pthread_cond_t的指针 cond_attr是一个指向结构pthread_condattr_t 的指针。结构pthread_condattr_t是条件变量的属性结构和互斥锁一样我们可以用它来设置条件变量是进程内可用还是进程间可用默认值是PTHREAD_PROCESS_PRIVATE即此条件变量被同一进程内的各个线程使用。注意初始化条件变量只有未被使用时才能重新初始化或被释放。释放一个条件变量的函数为pthread_cond_destroy pthread_cond_t cond 。

函数pthread_cond_wait  使线程阻塞在一个条件变量上。它的函数原型为extern int pthread_cond_wait__P((pthread_cond_t*__cond,pthread_mutex_t*__m utex));

线程解开m utex指向的锁并被条件变量cond阻塞。线程可以被函数pthread_cond_signal和函数pth read_cond_broadcast唤醒但是要注意的是条件变量只是起阻塞和唤醒线程的作用具体的判断条件还需用户给出例如一个变量是否为0等等这一点我们从后面的例子中可以看到。线程被唤醒后它将重新检查判断条件是否满足如果还不满足一般说来线程应该仍阻塞在这里被等待被下一次唤醒。这个过程一般用whi le语句实现。

另一个用来阻塞线程的函数是pthread_cond_timedwait   它的原型为extern int pthread_cond_tim edwait__P((pthread_cond_t*__cond,pthread_m utex_t*__m utex,__const struct ti m espec*__abstim e));

它比函数pthread_cond_wait  多了一个时间参数经历abstim e段时间后 即使条件变量不满足阻塞也被解除。

函数pthread_cond_s ignal  的原型为extern int pthread_cond_signal__P((pthread_cond_t*__cond));

它用来释放被阻塞在条件变量cond上的一个线程。多个线程阻塞在此条件变量上时哪一个线程被

Gcore(gcorelabs)俄罗斯海参崴VPS简单测试

有一段时间没有分享Gcore(gcorelabs)的信息了,这是一家成立于2011年的国外主机商,总部位于卢森堡,主要提供VPS主机和独立服务器租用等,数据中心包括俄罗斯、美国、日本、韩国、新加坡、荷兰、中国(香港)等多个国家和地区的十几个机房,商家针对不同系列的产品分为不同管理系统,比如VPS(Hosting)、Cloud等都是独立的用户中心体系,部落分享的主要是商家的Hosting(Virtu...

TMTHosting:夏季优惠,美国西雅图VPS月付7折,年付65折,美国服务器95折AS4837线路

tmthosting怎么样?tmthosting家本站也分享过多次,之前也是不温不火的商家,加上商家的价格略贵,之到斯巴达商家出现,这个商家才被中国用户熟知,原因就是斯巴达家的机器是三网回程AS4837线路,而且也没有多余的加价,斯巴达家断货后,有朋友发现TMTHosting竟然也在同一机房,所以大家就都入手了TMTHosting家的机器。目前,TMTHosting商家放出了夏季优惠,针对VPS推...

TmhHost暑假活动:高端线路VPS季付8折优惠,可选洛杉矶CN2 GIA/日本软银/香港三网CN2 GIA/韩国双向CN2等

tmhhost怎么样?tmhhost正在搞暑假大促销活动,全部是高端线路VPS,现在直接季付8折优惠,活动截止时间是8月31日。可选机房及线路有美国洛杉矶cn2 gia+200G高防、洛杉矶三网CN2 GIA、洛杉矶CERA机房CN2 GIA,日本软银(100M带宽)、香港BGP直连200M带宽、香港三网CN2 GIA、韩国双向CN2。点击进入:tmhhost官方网站地址tmhhost优惠码:Tm...

linux多线程编程为你推荐
集成显卡和独立显卡哪个好集成显卡和独立显卡什么区别?迈腾和帕萨特哪个好新帕萨特和新迈腾哪个好?电热水器和燃气热水器哪个好电热水器和燃气热水器哪个好?行车记录仪哪个好行车记录仪什么牌子好清理手机垃圾软件哪个好清理手机垃圾文件的软件哪个好?美国国际东西方大学美国新常春藤大学有哪些?牡丹江教育云空间登录云空间怎么登入qq空间登录器QQ空间校友网页自动登陆器willyunlee求几近完美演员表,几近完美女主角几近完美男主角是谁?360云盘登录360云盘在哪里登陆
虚拟主机控制面板 免费注册网站域名 n点虚拟主机管理系统 过期域名抢注 赵容 加勒比群岛 256m内存 163网 密码泄露 国外php空间 193邮箱 web服务器架设 腾讯实名认证中心 php空间购买 稳定免费空间 鲁诺 监控服务器 阵亡将士纪念日 广州主机托管 register.com 更多