linux 多线程编程.docx
C语言中,信号量的数据类型为结构sem_t,它本质上是一个长整型的数。thread1.c#include <pthread.h>#include <stdlib.h>#include <unistd.h> void *thread_function(void *arg) int i; for ( i=0; i<20; i+) printf("Thread says hi!n"); sleep(1); return NULL;int main(void) pthread_t mythread; if ( pthread_create( &mythread, NULL, thread_function, NULL) ) printf("error creating thread."); abort(); if ( pthread_join ( mythread, NULL ) ) printf("error joining thread."); abort(); exit(0);要编译这个程序,只需先将程序存为 thread1.c,然后输入:$ gcc thread1.c -o thread1 -lpthread运行则输入:$ ./thread1理解 thread1.cthread1.c 是一个非常简单的线程程序。虽然它没有实现什么有用的功能,但可以帮助理解线程的运行机制。下面,我们一步一步地了解这个程序是干什么的。main() 中声明了变量 mythread,类型是 pthread_t。pthread_t 类型在 pthread.h 中定义,通常称为“线程 id”(缩写为 "tid")。可以认为它是一种线程句柄。mythread 声明后(记住 mythread 只是一个 "tid",或是将要创建的线程的句柄),调用 pthread_create 函数创建一个真实活动的线程。不要因为 pthread_create() 在 "if" 语句内而受其迷惑。由于 pthread_create() 执行成功时返回零而失败时则返回非零值,将 pthread_create() 函数调用放在 if() 语句中只是为了方便地检测失败的调用。让我们查看一下 pthread_create 参数。第一个参数 &mythread 是指向 mythread 的指针。第二个参数当前为 NULL,可用来定义线程的某些属性。由于缺省的线程属性是适用的,只需将该参数设为 NULL。第三个参数是新线程启动时调用的函数名。本例中,函数名为 thread_function()。当 thread_function() 返回时,新线程将终止。本例中,线程函数没有实现大的功能。它仅将 "Thread says hi!" 输出 20 次然后退出。注意 thread_function() 接受 void * 作为参数,同时返回值的类型也是 void *。这表明可以用 void * 向新线程传递任意类型的数据,新线程完成时也可返回任意类型的数据。那如何向线程传递一个任意参数?很简单。只要利用 pthread_create() 中的第四个参数。本例中,因为没有必要将任何数据传给微不足道的 thread_function(),所以将第四个参数设为 NULL。您也许已推测到,在 pthread_create() 成功返回之后,程序将包含两个线程。等一等, 两个 线程?我们不是只创建了一个线程吗?不错,我们只创建了一个进程。但是主程序同样也是一个线程。可以这样理解:如果编写的程序根本没有使用 POSIX 线程,则该程序是单线程的(这个单线程称为“主”线程)。创建一个新线程之后程序总共就有两个线程了。我想此时您至少有两个重要问题。第一个问题,新线程创建之后主线程如何运行。答案,主线程按顺序继续执行下一行程序(本例中执行 "if (pthread_join(.)")。第二个问题,新线程结束时如何处理。答案,新线程先停止,然后作为其清理过程的一部分,等待与另一个线程合并或“连接”。现在,来看一下 pthread_join()。正如 pthread_create() 将一个线程拆分为两个, pthread_join() 将两个线程合并为一个线程。pthread_join() 的第一个参数是 tid mythread。第二个参数是指向 void 指针的指针。如果 void 指针不为 NULL,pthread_join 将线程的 void * 返回值放置在指定的位置上。由于我们不必理会 thread_function() 的返回值,所以将其设为 NULL.您会注意到 thread_function() 花了 20 秒才完成。在 thread_function() 结束很久之前,主线程就已经调用了 pthread_join()。如果发生这种情况,主线程将中断(转向睡眠)然后等待 thread_function() 完成。当 thread_function() 完成后, pthread_join() 将返回。这时程序又只有一个主线程。当程序退出时,所有新线程已经使用 pthread_join() 合并了。这就是应该如何处理在程序中创建的每个新线程的过程。如果没有合并一个新线程,则它仍然对系统的最大线程数限制不利。这意味着如果未对线程做正确的清理,最终会导致 pthread_create() 调用失败。thread2.c 的代码如下:thread2.c#include <pthread.h>#include <stdlib.h>#include <unistd.h>#include <stdio.h>int myglobal; void *thread_function(void *arg) int i,j; for ( i=0; i<20; i+) j=myglobal; j=j+1; printf("."); fflush(stdout); sleep(1); myglobal=j; return NULL;int main(void) pthread_t mythread; int i; if ( pthread_create( &mythread, NULL, thread_function, NULL) ) printf("error creating thread."); abort(); for ( i=0; i<20; i+) myglobal=myglobal+1; printf("o"); fflush(stdout); sleep(1); if ( pthread_join ( mythread, NULL ) ) printf("error joining thread."); abort(); printf("nmyglobal equals %dn",myglobal); exit(0);理解 thread2.c如同第一个程序,这个程序创建一个新线程。主线程和新线程都将全局变量 myglobal 加一 20 次。但是程序本身产生了某些意想不到的结果。编译代码请输入:$ gcc thread2.c -o thread2 -lpthread运行请输入:$ ./thread2输出:$ ./thread2.o.o.o.o.oo.o.o.o.o.o.o.o.o.o.o.o.o.o.omyglobal equals 21非常意外吧!因为 myglobal 从零开始,主线程和新线程各自对其进行了 20 次加一, 程序结束时 myglobal 值应当等于 40。由于 myglobal 输出结果为 21,这其中肯定有问题。首先查看函数 thread_function()。注意如何将 myglobal 复制到局部变量 "j" 了吗? 接着将 j 加一, 再睡眠一秒,然后到这时才将新的 j 值复制到 myglobal?这就是关键所在。设想一下,如果主线程就在新线程将 myglobal 值复制给 j 后 立即将 myglobal 加一,会发生什么?当 thread_function() 将 j 的值写回 myglobal 时,就覆盖了主线程所做的修改。thread3.c#include <pthread.h> #include <stdlib.h> #include <unistd.h> #include <stdio.h> int myglobal; pthread_mutex_t mymutex=PTHREAD_MUTEX_INITIALIZER; void *thread_function(void *arg) int i,j; for ( i=0; i<20; i+) pthread_mutex_lock(&mymutex); j=myglobal; j=j+1; printf("."); fflush(stdout); sleep(1); myglobal=j; pthread_mutex_unlock(&mymutex); return NULL; int main(void) pthread_t mythread; int i; if ( pthread_create( &mythread, NULL, thread_function, NULL) ) printf("error creating thread."); abort(); for ( i=0; i<20; i+) pthread_mutex_lock(&mymutex); myglobal=myglobal+1; pthread_mutex_unlock(&mymutex); printf("o"); fflush(stdout); sleep(1); if ( pthread_join ( mythread, NULL ) ) printf("error joining thread."); abort(); printf("nmyglobal equals %dn",myglobal); exit(0); 输出:o.ooooooooooooooooooomyglobal equals 40pthread_cond_wait() 用于阻塞当前线程,等待别的线程使用pthread_cond_signal()或pthread_cond_broadcast来唤醒它。 pthread_cond_wait() 必须与pthread_mutex 配套使用。pthread_cond_wait()函数一进入wait状态就会自动release mutex。当其他线程通过pthread_cond_signal()或pthread_cond_broadcast,把该线程唤醒,使pthread_cond_wait()通过(返回)时,该线程又自动获得该mutex。 pthread_cond_signal函数的作用是发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行.如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回。pthread_cond_signal只能唤醒已经处于pthread_cond_wait的线程 也就是说,如果signal的时候没有线程在condition wait,那么本次signal就没有效果,后续的线程进入condition wait之后,无法被之前的signal唤醒。 使用pthread_cond_signal一般不会有“惊群现象”产生,他最多只给一个线程发信号。假如有多个线程正在阻塞等待着这个条件变量的话,那么是根据各等待线程优先级的高低确定哪个线程接收到信号开始继续执行。如果各线程优先级相同,则根据等待时间的长短来确定哪个线程获得信号。但无论如何一个pthread_cond_signal调用最多发信一次。 但是pthread_cond_signal在多处理器上可能同时唤醒多个线程,当你只能让一个线程处理某个任务时,其它被唤醒的线程就需要继续 wait,而且规范要求pthread_cond_signal至少唤醒一个pthread_cond_wait上的线程,其实有些实现为了简单在单处理器上也会唤醒多个线程. 另外,某些应用,如线程池,pthread_cond_broadcast唤醒全部线程,但我们通常只需要一部分线程去做执行任务,所以其它的线程需要继续wait.所以强烈推荐对pthread_cond_wait() 使用while循环来做条件判断.图 4. 采用 Linux 条件变量模型的出租车实例流程通过对比结果,你会发现同样的逻辑,在 Linux 平台上运行的结果却完全是两样。对于在 Windows 平台上的模型一, Jack 开着出租车到了站台,触发条件变量。如果没顾客,条件变量将维持触发状态,也就是说 Jack 停下车在那里等着。直到 Susan 小姐来了站台,执行等待条件来找出租车。 Susan 搭上 Jack 的出租车离开,同时条件变量被自动复位。但是到了 Linux 平台,问题就来了,Jack 到了站台一看没人,触发的条件变量被直接复位,于是 Jack 排在等待队列里面。来迟一秒的 Susan 小姐到了站台却看不到在那里等待的 Jack,只能等待,直到 Mike 开车赶到,重新触发条件变量,Susan 才上了 Mike 的车。这对于在排队系统前面的 Jack 是不公平的,而问题症结是在于 Linux 平台上条件变量触发的自动复位引起的一个 Bug 。条件变量在 Linux 平台上的这种模型很难说好坏。但是在实际开发中,我们可以对代码稍加改进就可以避免这种差异的发生。由于这种差异只发生在触发没有被线程等待在条件变量的时刻,因此我们只需要掌握好触发的时机即可。最简单的做法是增加一个计数器记录等待线程的个数,在决定触发条件变量前检查下该变量即可。改进后 Linux 函数如清单 5 所示。清单 5. Linux 出租车案例代码实例 / 提示出租车到达的条件变量 pthread_cond_t taxiCond; / 同步锁 pthread_mutex_t taxiMutex; / 旅客人数,初始为 0 int travelerCount=0; / 旅客到达等待出租车 void * traveler_arrive(void * name) cout<< ” Traveler: ” <<(char *)name<< ” needs a taxi now! ” <<endl; pthread_mutex_lock(&taxiMutex); / 提示旅客人数增加 travelerCount+; pthread_cond_wait (&taxiCond, &taxiMutex); pthread_mutex_unlock (&taxiMutex); cout<< ” Traveler: ” << (char *)name << ” now got a taxi! ” <<endl; pthread_exit( (void *)0 ); / 出租车到达 void * taxi_arrive(void *name) cout<< ” Taxi ” <<(char *)name<< ” arrives. ” <<endl; while(true) pthread_mutex_lock(&taxiMutex); / 当发现已经有旅客在等待时,才触发条件变量 if(travelerCount>0) pthread_cond_signal(&taxtCond); pthread_mutex_unlock (&taxiMutex); break; pthread_mutex_unlock (&taxiMutex); pthread_exit( (void *)0 ); #include <sys/types.h>#include <sys/socket.h>#include <unistd.h>#include <stdio.h>#include <string.h>#include <netinet/in.h>#include <arpa/inet.h>#include <errno.h>#include <stdlib.h>#include <time.h>#include <pthread.h>void* testThreadPool(void *t);pthread_mutex_t clifd_mutex = PTHREAD_MUTEX_INITIALIZER;pthread_cond_t clifd_cond = PTHREAD_COND_INITIALIZER;int main() char b=9; pthread_t mythread; if (pthread_create(&mythread, NULL, (void*)testThreadPool, (void*)(&b) printf("pthread_createn"); sleep(2); printf("father : 1n"); pthread_mutex_lock(&clifd_mutex); sleep(2); printf("father : 2n"); pthread_cond_signal(&clifd_cond); sleep(2); printf("father : 3n"); pthread_mutex_unlock(&clifd_mutex); printf("father : 4n");void* testThreadPool(void *t) printf("child :t is %dn", (*(char *)t); printf("child :1n"); pthread_mutex_lock(&clifd_mutex); printf("child :2n"); pthread_cond_wait(&clifd_cond, &clifd_mutex); printf("child :3n"); pthread_mutex_unlock(&clifd_mutex); printf("child :4n"); printf("child :5n"); rootsong pthread_cont_wait# ./song child :t is 9child :1child :2father : 1father : 2father : 3child :3child :4child :5father : 4/#include <sys/types.h>#include <sys/socket.h>#include <unistd.h>#include <stdio.h>#include <string.h>#include <netinet/in.h>#include <arpa/inet.h>#include <errno.h>#include <stdlib.h>#include <time.h>#include <pthread.h>void* testThreadPool(void *t);void* song(void* s);pthread_mutex_t clifd_mutex = PTHREAD_MUTEX_INITIALIZER;pthread_cond_t clifd_cond = PTHREAD_COND_INITIALIZER;int main() char b=9; pthread_t mythread; pthread_t ssthread; if (pthread_create(&mythread, NULL, (void*)testThreadPool, (void*)(&b) printf("pthread_createn"); if (pthread_create(&ssthread, NULL, (void*)song, (void*)(&b) printf("pthread_createn"); sleep(2); printf("father : 1n"); pthread_mutex_lock(&clifd_mutex);sleep(2); printf("father : 2n"); pthread_cond_signal(&clifd_cond);sleep(2); printf("father : 3n"); pthread_mutex_unlock(&clifd_mutex);sleep(2); printf("father : 4n"); sleep(3);void* testThreadPool(void *t) printf("child :t is %dn", (*(char *)t); printf("child :1n"); pthread_mutex_lock(&clifd_mutex); printf("child :2n"); pthread_cond_wait(&clifd_cond, &clifd_mutex); printf("child :3n"); pthread_mutex_unlock(&clifd_mutex); printf("child :4n"); printf("child :5n"); pthread_cond_signal(&clifd_cond);void *song(void* s) printf("song:1n"); pthread_mutex_lock(&clifd_mutex); printf("song :2n"); pthread_cond_wait(&clifd_cond, &clifd_mutex); printf("song:3n"); pthread_mutex_unlock(&clifd_mutex); printf("song:4n"); printf("song:5n");rootsong pthread_cont_wait# ./song child :t is 9child :1child :2song:1song :2father : 1father : 2father : 3child :3child :4child :5song:3song:4song:5father : 4当pthread_mutex_lock()返回时,该互斥锁已被锁定。线程调用该函数让互斥锁上锁,如果该互斥锁已被另一个线程锁定和拥有,则调用该线程将阻塞,直到该互斥锁变为可用为止。 pthread_mutex_unlock(pthread_mutex_t *mutex); /该函数用来对一个互斥体解锁。如果当前线程拥有参数mutex 所 /指定的互斥体,该调用将该互斥体解锁int pthread_cond_broadcast( pthread_cond_t *cond ); 该函数用来对所有等待参数cond所指定的条件变量的线程解除阻塞,调用成功返回0,否则返回错误代码。线程条件变量pthread_cond_t int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex_t *attr); 动态初始化一互斥量在 pthread_mutex_init() 函数中:第一个参数 mutex 是指向要初始化的互斥锁的指针。第二个参数 mutexattr 是指向属性对象的指针,该属性对象定义要初始化的互斥锁的属性。如果该指针为 NULL,则使用默认的属性。 注:用常量来初始化 pthread_mutex_t mutex = PTHREAD_MUTE_INITIALIZER;(只对静态分配的互斥量)int pthread_cond_init(pthread_cond_t *cv,const pthread_condattr_t *cattr);pthread_cond_t cv = PTHREAD_COND_INITIALIZER;来初始化静态定义的条件变量,使其具有缺省属性。这和用pthread_cond_init函数动态分配的效果是一样的。初始化时不进行错误检查。一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被称为子进程(child process)。fork函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回0值而父进程中返回子进程ID。函数说明:创建并初始化有名信号灯。函数原型:sem_t *sem_open(const char *name,int oflag,mode_t mode,unsigned int value);参数:name 信号灯的外部名字 oflag 选择创建或打开一个现有的信号灯mode 权限位 value 信号灯初始值sem_wait() 减小(锁定)由sem指定的信号量的值.如果信号量的值比0大,那么进行减一的操作,函数立即返回.如果信号量当前为0值,那么调用就会一直阻塞直到或者是信号量变得可以进行减一的操作(例如,信号量的值比0大),或者是信号处理程序中断调用sem_post sem_post函数的作用是给信号量的值加上一个“1”,它是一个“原子操作”sem_init的使用sem_t g_sem_signal;sem_init(&g_sem_signal,0,0);sem_getvalue(&g_sem_signal, &val); sem_wait(&m_sem_in);sem_post(&m_sem_out);sem_open的使用sem_t *g_sem_signal;g_sem_signal = sem_open(m_signal_sem_name,OPEN_FLAG,0644,INIT_S);(参数的配置参照上面链接)sem_getvalue(g_sem_signal, &val);sem_wait(m_sem_in);sem_post(m_sem_out);sem_close用于关闭打开的信号量。当一个进程终止时,内核对其上仍然打开的所有有名信号量自动执行这个操作。调用sem_close关闭信号量并没有把它从系统中删除它,POSIX有名信号量是随内核持续的。即使当前没有进程打开某个信号量它的值依然保持。直到内核重新自举或调用sem_unlink()删除该信号量。sem_unlink用于将有名信号量立刻从系统中删除,但信号量的销毁是在所有进程都关闭信号量的时候。Share memory #include<unistd.h>#include<sys/ipc.h>#include<sys/shm.h>#include<stdio.h>#include<string.h> #define KEY 1234 #define SIZE 1024int main() int shmid; int *shmaddr; /char *shmaddr struct shmid_ds buf; shmid = shmget(KEY,SIZE,IPC_CREAT | 0600); if( fork() = 0 ) shmaddr =(int *)shmat(shmid,NULL,0); / shmaddr =(char *)shmat(shmid,NULL,0); *shmaddr = 1024; / strcpy(shmaddr,"hi! i am child process!n"); shmdt(shmaddr); return 0; else sleep(3); shmctl(shmid,IPC_STAT,&buf); printf("shm_segsz = %d bytesn",buf.shm_segsz); printf("shm_cpid = %dn",buf.shm_cpid); printf("shm_lpid = %dn",buf.shm_lpid); shmaddr =(int *)shmat(shmid,NULL,0); printf("%d",*shmaddr); / printf("%s",shmaddr) shmdt(shmaddr); shmctl(shmid,IPC_RMID,NULL); return 0;删除共享内存先ipcs -m 看看那些shmid该删然后ipcrm -m shmid在编译阶段,共享内存按你申请的空间进行分配内存,对越界不做判断,但是在执行过程中可能会发生溢出,程序不会抛出异常管道通信 #include <unistd.h> #include <stdio.h> int main( void ) int filedes2; char buf80; pid_t pid; pipe( filedes ); pid=fork(); if (pid > 0) printf( "This is in the father process,here write a string to the pipe.n" ); char s = "Hello world , this is write by pipe.n" write( filedes1, s, sizeof(s) ); close( filedes0 ); close( filedes1 ); else if(pid = 0) printf( "This is in the child process,here read a string from the pipe.n" ); read( filedes0, buf, sizeof(buf) ); printf( "%sn", buf ); close( filedes0 ); close( filedes1 );