1序言
线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者。传统的Unix也支持线程的概念,并且在一个进程(process)中只容许有一个线程,这样多线程就意味着多进程。现今,多线程技术早已被许多操作系统所支持,包括Windows/NT,其实,也包括Linux。
为何有了进程的概念后,还要再引入线程呢?使用多线程究竟有什么用处?哪些的系统应当选用多线程?我们首先必须回答那些问题。
使用多线程的理由之一是和进程相比,它是一种十分"俭朴"的多任务操作方法。我们晓得,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,完善诸多的数据表来维护它的代码段、堆栈段和数据段,这是一种"高昂"的多任务工作方法。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部份数据,启动一个线程所耗费的空间远远大于启动一个进程所耗费的空间,并且,线程间彼此切换所需的时间也远远大于进程间切换所须要的时间。据统计linux c程序基础与实例讲解,总的说来,一个进程的开支大概是一个线程开支的30倍左右,其实,在具体的系统上,这个数据可能会有较大的区别。
使用多线程的理由之二是线程间便捷的通讯机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通讯的形式进行,这些方法除了费时,但是很不便捷。线程则不然,因为同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这除了快捷,但是便捷。其实,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所更改,有的子程序中申明为static的数据更有可能给多线程程序带来灾难性的严打,那些正是编撰多线程程序时最须要注意的地方。
不仅以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方法,其实有以下的优点:
1)提升应用程序响应。这对图形界面的程序尤其有意义,当一个操作历时很长时,整个系统就会等待这个操作,此时程序不会响应鼠标、鼠标、菜单的操作,而使用多线程技术,将历时长的操作(timeconsuming)放在一个新的线程,可以防止这些难堪的情况。
2)使多CPU系统愈发有效。操作系统会保证当线程数不小于CPU数量时,不同的线程运行于不同的CPU上。
3)改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部份,这样的程序会利于理解和更改。
下边我们先来尝试编撰一个简单的多线程程序。
2简单的多线程编程
Linux系统下的多线程遵守POSIX线程插口,称为pthread。编撰Linux下的多线程程序,须要使用头文件pthread.h,联接时须要使用库libpthread.a。顺便说一下,Linux下pthread的实现是通过系统调用clone()来实现的。clone()是Linux所特有的系统调用,它的使用方法类似fork,关于clone()的详尽情况,有兴趣的读者可以去查看有关文档说明。下边我们展示一个最简单的多线程程序example1.c。
/* example.c*/ #include #include void thread(void) { int i; for(i=0;i<3;i++) printf("This is a pthread./n"); } int main(void) { pthread_t id; int i,ret; ret=pthread_create(&id,NULL,(void *) thread,NULL); if(ret!=0) { printf ("Create pthread error!/n"); exit (1); } for(i=0;i<3;i++) printf("This is the main process./n"); pthread_join(id,NULL); return (0); }
我们编译此程序:
gccexample1.c-lpthread-oexample1
运行example1,我们得到如下结果:
Thisisthemainprocess.
Thisisapthread.
Thisisthemainprocess.
Thisisthemainprocess.
Thisisapthread.
Thisisapthread.
再度运行,我们可能得到如下结果:
Thisisapthread.
Thisisthemainprocess.
Thisisapthread.
Thisisthemainprocess.
Thisisapthread.
Thisisthemainprocess.
前后两次结果不一样,这是两个线程角逐CPU资源的结果。前面的示例中,我们使用到了两个函数,pthread_create和pthread_join,并申明了一个pthread_t型的变量。
pthread_t在头文件/usr/include/bits/pthreadtypes.h中定义:
typedefunsignedlongintpthread_t;
它是一个线程的标示符。函数pthread_create拿来创建一个线程,它的原型为:
externintpthread_create__P((pthread_t*__thread,__constpthread_attr_t*__attr,
void*(*__start_routine)(void*),void*__arg));
第一个参数为指向线程标示符的表针,第二个参数拿来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数。这儿,我们的函数thread不须要参数,所以最后一个参数设为空表针。第二个参数我们也设为空表针,这样将生成默认属性的线程。对线程属性的设定和更改我们将在下一节详述。当创建线程成功时,函数返回0,若不为0则说明创建线程失败,常见的错误返回代码为EAGAIN和EINVAL。后者表示系统限制创建新的线程,比如线程数量过多了;前者表示第二个参数代表的线程属性值非法。创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原先的线程则继续运行下一行代码。
函数pthread_join拿来等待一个线程的结束。函数原型为:
externintpthread_join__P((pthread_t__th,void**__thread_return));
第一个参数为被等待的线程标示符,第二个参数为一个用户定义的表针,它可以拿来储存被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将仍然等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。一个线程的结束有两种途径,一种是象我们里面的事例一样,函数结束了,调用它的线程也就结束了;另一种方法是通过函数pthread_exit来实现。它的函数原型为:
externvoidpthread_exit__P((void*__retval))__attribute__((__noreturn__));
惟一的参数是函数的返回代码,只要pthread_join中的第二个参数thread_return不是NULL,这个值将被传递给thread_return。最后要说明的是,一个线程不能被多个线程等待,否则第一个接收到讯号的线程成功返回,其余调用pthread_join的线程则返回错误代码ESRCH。
在这一节里,我们编撰了一个最简单的线程,并把握了最常用的三个函数pthread_create,pthread_join和pthread_exit。下边,我们来了解线程的一些常用属性以及怎样设置这种属性。
3更改线程的属性
在上一节的事例里,我们用pthread_create函数创建了一个线程,在这个线程中,我们使用了默认参数,正式该函数的第二个参数设为NULL。的确,对大多数程序来说,使用默认属性就够了,但我们还是有必要来了解一下线程的有关属性。
属性结构为pthread_attr_t,它同样在头文件/usr/include/pthread.h中定义,喜欢追根问底的人可以自己去查看。属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。属性对象主要包括是否绑定、是否分离、堆栈地址、堆栈大小、优先级。默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。
关于线程的绑定,牵连到另外一个概念:轻进程(LWP:LightWeightProcess)。轻进程可以理解为内核线程,它坐落用户层和系统层之间。系统对线程资源的分配、对线程的控制是通过轻进程来实现的,一个轻进程可以控制一个或多个线程。默认状况下,启动多少轻进程、哪些轻进程来控制什么线程是由系统来控制的,这些状况即称为非绑定的。绑定状况下,则顾名思义,即某个线程固定的"绑"在一个轻进程之上。被绑定的线程具有较高的响应速率,这是由于CPU时间片的调度是面向轻进程的,绑定的线程可以保证在须要的时侯它总有一个轻进程可用。通过设置被绑定的轻进程的优先级和调度级可以促使绑定的线程满足例如实时反应之类的要求。
设置线程绑定状态的函数为pthread_attr_setscope,它有两个参数,第一个是指向属性结构的表针,第二个是绑定类型,它有两个取值:PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)。下边的代码即创建了一个绑定的线程。
#include pthread_attr_t attr; pthread_t tid; /*初始化属性值,均设为默认值*/ pthread_attr_init(&attr); pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); pthread_create(&tid, &attr, (void *) my_function, NULL);
线程的分离状态决定一个线程以哪些样的方法来中止自己。在前面的事例中,我们采用了线程的默认属性,即为非分离状态,这些情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算中止,能够释放自己占用的系统资源。而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就中止了,马上释放系统资源。程序员应当按照自己的须要,选择适当的分离状态。设置线程分离状态的函数为pthread_attr_setdetachstate(pthread_attr_t*attr,intdetachstate)。第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和PTHREAD_CREATE_JOINABLE(非分离线程)。这儿要注意的一点是,假如设置一个线程为分离线程,而这个线程运行又特别快,它很可能在pthread_create函数返回之前就中止了,它中止之后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。要避开这些情况可以采取一定的同步举措,最简单的方式之一是可以在被创建的线程里调用pthread_cond_timewait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。设置一段等待时间,是在多线程编程里常用的技巧。并且注意不要使用例如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。
另外一个可能常用的属性是线程的优先级,它储存在结构sched_param中。用函数pthread_attr_getschedparam和函数pthread_attr_setschedparam进行储存,通常说来,我们总是先取优先级,对取得的值更改后再储存回来。下边即是一段简单的反例。
/* example.c*/ #include #include void thread(void) { int i; for(i=0;i<3;i++) printf("This is a pthread./n"); } int main(void) { pthread_t id; int i,ret; ret=pthread_create(&id,NULL,(void *) thread,NULL); if(ret!=0) { printf ("Create pthread error!/n"); exit (1); } for(i=0;i<3;i++) printf("This is the main process./n"); pthread_join(id,NULL); return (0); }
4线程的数据处理
和进程相比,线程的最大优点之一是数据的共享性,各个进程共享父进程处沿用的数据段,可以便捷的获得、修改数据。但这也给多线程编程带来了许多问题。我们必须小心有多个不同的进程访问相同的变量。许多函数是不可重入的,即同时不能运行一个函数的多个拷贝(除非使用不同的数据段)。在函数中申明的静态变量往往带来问题,函数的返回值也会有问题。由于假如返回的是函数内部静态申明的空间的地址,则在一个线程调用该函数得到地址后使用该地址指向的数据时,别的线程可能调用此函数并更改了这一段数据。在进程中共享的变量必须用关键字volatile来定义,这是为了避免编译器在优化时(如gcc中使用-OX参数)改变它们的使用方法。为了保护变量,我们必须使用讯号量、互斥等方式来保证我们对变量的正确使用。下边,我们就逐渐介绍处理线程数据时的有关知识。
4.1线程数据
在单线程的程序里,有两种基本的数据:全局变量和局部变量。但在多线程程序里,还有第三种数据类型:线程数据(TSD:Thread-SpecificData)。它和全局变量很象,在线程内部,各个函数可以象使用全局变量一样调用它,但它对线程外部的其它线程是不可见的。这些数据的必要性是显而易见的。比如我们常见的变量errno,它返回标准的出错信息。它似乎不能是一个局部变量,几乎每位函数都应当可以调用它;但它又不能是一个全局变量,否则在A线程里输出的很可能是B线程的出错信息。要实现诸这么类的变量,我们就必须使用线程数据。我们为每位线程数据创建一个键,它和这个键相关联,在各个线程里,都使用这个键来指代线程数据,但在不同的线程里,这个键代表的数据是不同的,在同一个线程里,它代表同样的数据内容。
和线程数据相关的函数主要有4个:创建一个键;为一个键指定线程数据;从一个键读取线程数据;删掉键。
创建键的函数原型为:
externintpthread_key_create__P((pthread_key_t*__key,
void(*__destr_function)(void*)));
第一个参数为指向一个键名的表针,第二个参数指明了一个destructor函数,假若这个参数不为空,这么当每位线程结束时,系统将调用这个函数来释放绑定在这个键上的显存块。这个函数常和函数pthread_once((pthread_once_t*once_control,void(*initroutine)(void)))一起使用,为了让这个键只被创建一次。函数pthread_once申明一个初始化函数,第一次调用pthread_once时它执行这个函数,之后的调用将被它忽视。
在下边的事例中,我们创建一个键,并将它和某个数据相关联。我们要定义一个函数createWindow,这个函数定义一个图形窗口(数据类型为Fl_Window*,这是图形界面开发工具FLTK中的数据类型)。因为各个线程还会调用这个函数,所以我们使用线程数据。
/* example.c*/ #include #include void thread(void) { int i; for(i=0;i<3;i++) printf("This is a pthread./n"); } int main(void) { pthread_t id; int i,ret; ret=pthread_create(&id,NULL,(void *) thread,NULL); if(ret!=0) { printf ("Create pthread error!/n"); exit (1); } for(i=0;i<3;i++) printf("This is the main process./n"); pthread_join(id,NULL); return (0); }
这样,在不同的线程中调用函数createMyWin,都可以得到在线程内部均可见的窗口变量,这个变量通过函数pthread_getspecific得到。在前面的事例中,我们早已使用了函数pthread_setspecific来将线程数据和一个键绑定在一起。这两个函数的原型如下:
externintpthread_setspecific__P((pthread_key_t__key,__constvoid*__pointer));
externvoid*pthread_getspecific__P((pthread_key_t__key));
这两个函数的参数意义和使用方式是显而易见的。要注意的是,用pthread_setspecific为一个键指定新的线程数据时,必须自己释放原有的线程数据以回收空间。这个过程函数pthread_key_delete拿来删掉一个键,这个键占用的显存将被释放,但同样要注意的是,它只释放键占用的显存,并不释放该键关联的线程数据所占用的显存资源,但是它也不会触发函数pthread_key_create中定义的destructor函数。线程数据的释放必须在释放键之前完成。
4.2互斥锁
互斥锁拿来保证一段时间内只有一个线程在执行一段代码。必要性显而易见:假定各个线程向同一个文件次序写入数据,最后得到的结果一定是灾难性的。
我们先看下边一段代码。这是一个读/写程序,它们公用一个缓冲区,但是我们假设一个缓冲区只能保存一条信息。即缓冲区只有两个状态:有信息或没有信息。
void reader_function ( void ); void writer_function ( void ); char buffer; int buffer_has_item=0; pthread_mutex_t mutex; struct timespec delay; void main ( void ){ pthread_t reader; /* 定义延迟时间*/ delay.tv_sec = 2; delay.tv_nec = 0; /* 用默认属性初始化一个互斥锁对象*/ pthread_mutex_init (&mutex,NULL); pthread_create(&reader, pthread_attr_default, (void *)&reader_function), NULL); writer_function( ); } void writer_function (void){ while(1){ /* 锁定互斥锁*/ pthread_mutex_lock (&mutex); if (buffer_has_item==0){ buffer=make_new_item( ); buffer_has_item=1; } /* 打开互斥锁*/ pthread_mutex_unlock(&mutex); pthread_delay_np(&delay); } } void reader_function(void){ while(1){ pthread_mutex_lock(&mutex); if(buffer_has_item==1){ consume_item(buffer); buffer_has_item=0; } pthread_mutex_unlock(&mutex); pthread_delay_np(&delay); } }
这儿申明了互斥锁变量mutex,结构pthread_mutex_t为不公开的数据类型,其中包含一个系统分配的属性对象。函数pthread_mutex_init拿来生成一个互斥锁。NULL参数表明使用默认属性。假如须要申明特定属性的互斥锁,须调用函数pthread_mutexattr_init。函数pthread_mutexattr_setpshared和函数pthread_mutexattr_settype拿来设置互斥锁属性。前一个函数设置属性pshared,它有两个取值,PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。后者拿来不同进程中的线程同步,前者用于同步本进程的不同线程。在前面的事例中,我们使用的是默认属性PTHREAD_PROCESS_PRIVATE。前者拿来设置互斥锁类型,可选的类型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE和PTHREAD_MUTEX_DEFAULT。它们分别定义了不同的上所、解锁机制,通常情况下,选用最后一个默认属性。
pthread_mutex_lock申明开始用互斥锁上锁,随后的代码直到调用pthread_mutex_unlock为止,均被上锁,即同一时间只能被一个线程调用执行。当一个线程执行到pthread_mutex_lock处时,假如该锁此时被另一个线程使用,那此线程被阻塞,即程序将等待到另一个线程释放此互斥锁。在前面的事例中,我们使用了pthread_delay_np函数,让线程睡眠一段时间,就是为了避免一个线程仍然抢占此函数。
里面的事例极其简单,就不再介绍了,须要提出的是在使用互斥锁的过程中很有可能会出现死锁:两个线程企图同时占用两个资源,并按不同的顺序锁定相应的互斥锁,比如两个线程都须要锁定互斥锁1和互斥锁2,a线程先锁定互斥锁1linux c程序基础与实例讲解,b线程先锁定互斥锁2,这时就出现了死锁。此时我们可以使用函数pthread_mutex_trylock,它是函数pthread_mutex_lock的非阻塞版本,当它发觉死锁不可防止时,它会返回相应的信息,程序员可以针对死锁作出相应的处理。另外不同的互斥锁类型对死锁的处理不一样,但最主要的还是要程序员自己在程序设计注意这一点。
4.3条件变量
前一节中我们述说了怎样使用互斥锁来实现线程间数据的共享和通讯,互斥锁一个显著的缺点是它只有两种状态:锁定和非锁定。而条件变量通过容许线程阻塞和等待另一个线程发送讯号的方式填补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被拿来阻塞一个线程,当条件不满足时,线程常常解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤起一个或多个正被此条件变量阻塞的线程。这种线程将重新锁定互斥锁并重新测试条件是否满足。通常说来,条件变量被拿来进行线承间的同步。
条件变量的结构为pthread_cond_tlinux是什么系统,函数pthread_cond_init()被拿来初始化一个条件变量。它的原型为:
externintpthread_cond_init__P((pthread_cond_t*__cond,__constpthread_condattr_t*__cond_attr));
其中cond是一个指向结构pthread_cond_t的表针,cond_attr是一个指向结构pthread_condattr_t的表针。结构pthread_condattr_t是条件变量的属性结构,和互斥锁一样我们可以用它来设置条件变量是进程内可用还是进程间可用,默认值是PTHREAD_PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用。注意初始化条件变量只有未被使用时才会重新初始化或被释放。释放一个条件变量的函数为pthread_cond_destroy(pthread_cond_tcond)。
函数pthread_cond_wait()使线程阻塞在一个条件变量上。它的函数原型为:
externintpthread_cond_wait__P((pthread_cond_t*__cond,
pthread_mutex_t*__mutex));
线程解开mutex指向的锁并被条件变量cond阻塞。线程可以被函数pthread_cond_signal和函数pthread_cond_broadcast唤起,然而要注意的是,条件变量只是起阻塞和唤起线程的作用,具体的判定条件还需用户给出,比如一个变量是否为0等等,这一点我们从旁边的事例中可以听到。线程被唤起后,它将重新检测判定条件是否满足,假如还不满足,通常说来线程应当仍阻塞在这儿,被等待被下一次唤起。这个过程通常用while句子实现。
另一个拿来阻塞线程的函数是pthread_cond_timedwait(),它的原型为:
externintpthread_cond_timedwait__P((pthread_cond_t*__cond,
pthread_mutex_t*__mutex,__conststructtimespec*__abstime));
它比函数pthread_cond_wait()多了一个时间参数,经历abstime段时间后,虽然条件变量不满足,阻塞也被解除。
函数pthread_cond_signal()的原型为:
externintpthread_cond_signal__P((pthread_cond_t*__cond));
它拿来释放被阻塞在条件变量cond上的一个线程。多个线程阻塞在此条件变量上时,哪一个线程被唤起是由线程的调度策略所决定的。要注意的是,必须用保护条件变量的互斥锁来保护这个函数,否则条件满足讯号又可能在测试条件和调用pthread_cond_wait函数之间被发出,进而导致无限制的等待。下边是使用函数pthread_cond_wait()和函数pthread_cond_signal()的一个简单的反例。
pthread_mutex_t count_lock; pthread_cond_t count_nonzero; unsigned count; decrement_count () { pthread_mutex_lock (&count_lock); while(count==0) pthread_cond_wait( &count_nonzero, &count_lock); count=count -1; pthread_mutex_unlock (&count_lock); } increment_count(){ pthread_mutex_lock(&count_lock); if(count==0) pthread_cond_signal(&count_nonzero); count=count+1; pthread_mutex_unlock(&count_lock); }
count值为0时,decrement函数在pthread_cond_wait处被阻塞,并打开互斥锁count_lock。此时,当调用到函数increment_count时,pthread_cond_signal()函数改变条件变量,告知decrement_count()停止阻塞。读者可以试着让两个线程分别运行这两个函数,瞧瞧会出现哪些样的结果。
函数pthread_cond_broadcast(pthread_cond_t*cond)拿来唤起所有被阻塞在条件变量cond上的线程。这种线程被唤起后将再度竞争相应的互斥锁,所以必须留神使用这个函数。
4.4讯号量
讯号量本质上是一个非负的整数计数器,它被拿来控制对公共资源的访问。当公共资源降低时,调用函数sem_post()降低讯号量。只有当讯号量值小于0时,才会使用公共资源,使用后,函数sem_wait()降低讯号量。函数sem_trywait()和函数pthread_mutex_trylock()起同样的作用,它是函数sem_wait()的非阻塞版本。下边我们挨个介绍和讯号量有关的一些函数,它们都在头文件/usr/include/semaphore.h中定义。
讯号量的数据类型为结构sem_t,它本质上是一个长整型的数。函数sem_init()拿来初始化一个讯号量。它的原型为:
externintsem_init__P((sem_t*__sem,int__pshared,unsignedint__value));
sem为指向讯号量结构的一个表针;pshared不为0时此讯号量在进程间共享,否则只能为当前进程的所有线程共享;value给出了讯号量的初始值。
函数sem_post(sem_t*sem)拿来降低讯号量的值。当有线程阻塞在这个讯号量上时,调用这个函数会使其中的一个线程不在阻塞,选择机制同样是由线程的调度策略决定的。
函数sem_wait(sem_t*sem)被拿来阻塞当前线程直至讯号量sem的值小于0,解除阻塞后将sem的值减一,表明公共资源经使用后减轻。函数sem_trywait(sem_t*sem)是函数sem_wait()的非阻塞版本,它直接将讯号量sem的值减一。
函数sem_destroy(sem_t*sem)拿来释放讯号量sem。
下边我们来看一个使用讯号量的反例。在这个事例中,一共有4个线程,其中两个线程负责从文件读取数据到公共的缓冲区,另两个线程从缓冲区读取数据作不同的处理(加和乘运算)。
/* File sem.c */ #include #include #include #define MAXSTACK 100 int stack[MAXSTACK][2]; int size=0; sem_t sem; /* 从文件1.dat读取数据,每读一次,信号量加一*/ void ReadData1(void){ FILE *fp=fopen("1.dat","r"); while(!feof(fp)){ fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]); sem_post(&sem); ++size; } fclose(fp); } /*从文件2.dat读取数据*/ void ReadData2(void){ FILE *fp=fopen("2.dat","r"); while(!feof(fp)){ fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]); sem_post(&sem); ++size; } fclose(fp); } /*阻塞等待缓冲区有数据,读取数据后,释放空间,继续等待*/ void HandleData1(void){ while(1){ sem_wait(&sem); printf("Plus:%d+%d=%d/n",stack[size][0],stack[size][1], stack[size][0]+stack[size][1]); --size; } } void HandleData2(void){ while(1){ sem_wait(&sem); printf("Multiply:%d*%d=%d/n",stack[size][0],stack[size][1], stack[size][0]*stack[size][1]); --size; } } int main(void){ pthread_t t1,t2,t3,t4; sem_init(&sem,0,0); pthread_create(&t1,NULL,(void *)HandleData1,NULL); pthread_create(&t2,NULL,(void *)HandleData2,NULL); pthread_create(&t3,NULL,(void *)ReadData1,NULL); pthread_create(&t4,NULL,(void *)ReadData2,NULL); /* 防止程序过早退出,让它在此无限期等待*/ pthread_join(t1,NULL); }
在Linux下,我们用命令gcc-lpthreadsem.c-osem生成可执行文件sem。我们事先编辑好数据文件1.dat和2.datlinux操作系统,假定它们的内容分别为12345678910和-1-2-3-4-5-6-7-8-9-10,我们运行sem,得到如下的结果:
Multiply:-1*-2=2
Plus:-1+-2=-3
Multiply:9*10=90
Plus:-9+-10=-19
Multiply:-7*-8=56
Plus:-5+-6=-11
Multiply:-3*-4=12
Plus:9+10=19
Plus:7+8=15
Plus:5+6=11
从中我们可以看出各个线程间的竞争关系。而数值并未按我们先前的次序显示下来这是因为size这个数值被各个线程任意更改的缘故。这也常常是多线程编程要注意的问题。
5小结
多线程编程是一个很有意思也很有用的技术,使用多线程技术的网路蚂蚁是目前最常用的下载工具之一,使用多线程技术的grep比单线程的grep要快上几倍,类似的事例还有好多。希望你们能用多线程技术写出高效实用的好程序来。
到此这篇关于Linux下的多线程编程实例解析的文章就介绍到这了,更多相关Linux下的多线程编程内容请搜索云海天教程先前的文章或继续浏览下边的相关文章希望你们之后多多支持云海天教程!
原文链接: