首页 > 代码库 > <<APUE>> 线程
<<APUE>> 线程
一个进程在同一时刻只能做一件事情,线程可以把程序设计成在同一时刻能够做多件事情,每个线程处理各自独立的任务。线程包括了表示进程内执行环境必需的信息,包括进程中标识线程的线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno变量以及线程似有数据。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本、程序的全局内存、栈及文件描述符。
使用线程的好处:(1)为每种事件分配单独的线程、能够简化处理异步事件的代码;(2)多个线程自动地可以访问相同的存储地址空间和文件描述符;(3)将一个问题分解为多个程序,改善整个程序的吞吐量;(4)使用多线程改善交互程序的响应时间。
进程与线程关系:进程是系统中程序执行和资源分配的基本单位。每个进程有自己的数据段、代码段和堆栈段。线程通常叫做轻型的进程。线程是在共享内存空间中并发执行的多道执行路径,他们共享一个进程的资源。因为线程和进程比起来很小,所以相对来说,线程花费更少的CPU资源。
1、线程标识
进程ID在整个系统中时唯一的,但线程ID只在它所属的进程环境中有效。线程ID用pthread_t数据类型来表示,实现的时候用一个结构来代表pthread_t数据类型。线程ID操作函数如下:
#include <pthread.h>
int pthread_equal(pthread_t t1, pthread_t t2);//比较两个线程ID
pthread_t pthread_self(void); //获取调用线程ID
2、线程创建
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
thread指向的内存单元被设置为新创建线程的ID,attr用于指定线程属性,start_routine是新线程开始执行的函数地址,arg是函数参数。如果是多个参数,可以将把参数存放在一个结构中,然后将结构地址传递给arg。线程创建时不能保证哪个线程会先运行。写个程序创建一个线程,输出线程标识符,程序如下:
由于pthread库不是Linux系统默认的库,连接时需要使用库libpthread.a,所以在使用pthread_create创建线程时,在编译中要加-lpthread参数。执行结果如下:
3、线程终止
如果进程中的任一个线程调用了exit、_exit、_Exit函数,那么整个进程就会终止。单个线程终止方式:(1)线程只是从启动例程中返回,返回值是线程的退出码;(2)线程可以被同一进程的其他线程取消;(3)线程调用pthread_exit函数。线程终止函数原型如下:
void pthread_exit(void *retval); //retval终止状体
int pthread_join(pthread_t thread, void **retval); //获取线程终止状态
其他线程通过调用pthread_join函数获取线程终止状态,调用线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者被取消。写个程序创建两个线程,获取线程退出状态。程序如下:
1 #include <pthread.h> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <unistd.h> 5 #include <errno.h> 6 #include <sys/types.h> 7 8 void* thread_func1(void *arg); 9 void* thread_func2(void *arg);10 11 int main()12 {13 pthread_t tid1;14 pthread_t tid2;15 int err;16 void *tret;17 //创建新线程118 err = pthread_create(&tid1,NULL,thread_func1,NULL);19 if(err != 0)20 {21 perror("pthread_create() error");22 exit(-1);23 }24 //创建新线程225 err = pthread_create(&tid2,NULL,thread_func2,NULL);26 if(err != 0)27 {28 perror("pthread_create() error");29 exit(-1);30 }31 //等待线程1终止32 err = pthread_join(tid1,&tret);33 if(err != 0)34 {35 perror("pthread_join error");36 exit(-1);37 }38 printf("thread1 exit code %d\n",(int)tret);39 //等待线程2终止40 err = pthread_join(tid2,&tret);41 if(err != 0)42 {43 perror("pthread_join error");44 exit(-1);45 }46 printf("thread2 exit code %d\n",(int)tret);47 exit(0);48 }49 void* thread_func1(void *arg)50 {51 printf("thread1 is returning.\n");52 return ((void*)1);53 }54 void* thread_func2(void *arg)55 {56 printf("thread2 exiting.\n");57 pthread_exit((void*)2);58 }
程序执行结果如下:
需要注意的是pthread_create和pthread_exit函数的无类型指针参数能够传递的数值可以不止一个,该指针可以传递包含复杂信息的结构地址,这个结构必须所使用的内存必须在调用者用完以后必须仍然有效,否则会出现无法访问或非法。例如在线程的栈上分配了该结构,例如下面程序,参数不正确使用,导致结果错误,程序如下:
1 #include <pthread.h> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <unistd.h> 5 #include <errno.h> 6 #include <sys/types.h> 7 8 struct foo 9 {10 int a;11 int b;12 int c;13 int d;14 };15 void printfoo(const char* str,const struct foo *fp);16 void* thread_func1(void *arg);17 void* thread_func2(void *arg);18 19 int main()20 {21 pthread_t tid1;22 pthread_t tid2;23 int err;24 struct foo *pfoo;25 err = pthread_create(&tid1,NULL,thread_func1,NULL);26 if(err != 0)27 {28 perror("pthread_create() error");29 exit(-1);30 }31 err = pthread_join(tid1,(void*)&pfoo);32 if(err != 0)33 {34 perror("pthread_join error");35 exit(-1);36 }37 sleep(1);38 printf("Parent starting second thread.\n");39 err = pthread_create(&tid2,NULL,thread_func2,NULL);40 if(err != 0)41 {42 perror("pthread_create() error");43 exit(-1);44 }45 sleep(1);46 printfoo("parent: ",pfoo);47 exit(0);48 }49 void printfoo(const char* str,const struct foo *fp)50 {51 puts(str);52 printf(" structure at 0x%x\n",(unsigned int)fp);53 printf("foo.a = %d\n",fp->a);54 printf("foo.b = %d\n",fp->b);55 printf("foo.c = %d\n",fp->c);56 printf("foo.d = %d\n",fp->d);57 }58 59 void* thread_func1(void *arg)60 {61 struct foo f = {1,2,3,4};62 printfoo("thread1:",&f);63 pthread_exit((void*)&f);64 }65 void* thread_func2(void *arg)66 {67 printf("thread2: ID is %u\n",(unsigned int)pthread_self());68 pthread_exit((void*)2);69 }
程序执行结果如下:
从结果可以看出,创建第一个线程时候,在该线程的栈中创建了一个结构,第二个线程栈覆盖了第一个线程栈,可以通过使用全局变量结构或者是使用malloc动态分配结构。
线程调用pthread_cancel函数来请求取消同一进程中的其他线程,并不等待线程终止,只是提出请求而已。函数原型为 int pthread_cancel(pthread_t tid)。函数功能等价于使得tid标识的线程调用pthread_exit(PTHREAD_CANCELED)。
线程清理处理程序,类似进程退出时候清理函数,线程可以建立多个清理处理程序,处理程序记录在栈中,执行顺序与注册顺序相反。函数原型如下:
void pthread_cleanup_push(void (*routine)(void *),void *arg); //注册清理函数
void pthread_cleanup_pop(int execute); //删除清理程序,若execute=0,清理函数将不被调用
两个函数限制:必须在线程相同的作用域中以匹配队的形式使用
清理函数在以下三种情况会调用:(1)调用pthread_exit;(2)响应取消请求;(3)用非零execute参数调用pthread_cleanup_pop。pthread_cleanup_pop函数删除上次pthread_cleanup_push调用建立的清理处理程序。
写个程序测试调用线程清理程序,程序如下:
程序执行结果如下:
从结果可以看出线程1的清理处理程序没有被调用,线程2的清理处理程序调用序列与注册序列相反。
进程原语与线程原语的比较
进程原语 | 线程原语 | 描述 |
fork | pthread_create | 创建新的控制流 |
exit | pthread_exit | 从现有的控制流中退出 |
waitpid | pthread_join | 从控制流中得到退出状态 |
atexit | pthread_cleanup_push | 注册在退出控制流时调用的函数 |
getpid | pthread_self | 获取控制流的ID |
abort | pthread_cancel | 请求控制流的非正常退出 |
4、线程同步
当多个控制线程共享相同的内存时,需要确保每个线程看到一致的数据视图。只有多个线程存在同时读写同一变量时,需要对线程进行同步 。线程同步的方法:线程锁(互斥量)、读写锁、条件变量。
如果对多个线程访问共同数据时,不加同步控制,会出什么问题呢?如下程序,两个线程对一个共享的数据结构进行操作,结果是不确定的。
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <errno.h> 5 #include <sys/types.h> 6 #include <pthread.h> 7 8 struct foo 9 {10 int f_count;11 };12 13 struct foo* foo_alloc(void);14 void foo_add(struct foo *fp);15 void foo_release(struct foo *fp);16 17 void * thread_func1(void *arg);18 void * thread_func2(void *arg);19 int main()20 {21 pthread_t pid1,pid2;22 int err;23 void *pret;24 struct foo *fobj;25 fobj = foo_alloc();26 //创建新线程1,函数地址为thread_fun1,参数为fobj27 err = pthread_create(&pid1,NULL,thread_func1,(void*)fobj);28 if(err != 0)29 {30 perror("pthread_create() error");31 exit(-1);32 }33 ////创建新线程2,函数地址为thread_fun2,参数为fobj34 err = pthread_create(&pid2,NULL,thread_func2,(void*)fobj);35 if(err != 0)36 {37 perror("pthread_create() error");38 exit(-1);39 }40 //等待线程退出41 pthread_join(pid1,&pret);42 printf("thread 1 exit code is: %d\n",(int)pret);43 pthread_join(pid2,&pret);44 printf("thread 2 exit code is: %d\n",(int)pret);45 exit(0);46 }47 //初始化48 struct foo* foo_alloc(void)49 {50 struct foo *fobj;51 fobj = (struct foo*)malloc(sizeof(struct foo));52 if(fobj != NULL)53 fobj->f_count = 0;54 return fobj;55 }56 void foo_add(struct foo *fp)57 {58 fp->f_count++;59 printf("f_count = %d\n",fp->f_count);60 }61 void foo_release(struct foo *fp)62 {63 fp->f_count--;64 printf("f_count = %d\n",fp->f_count);65 }66 void * thread_func1(void *arg)67 {68 struct foo *fp = (struct foo*)arg;69 printf("thread 1 start.\n");70 foo_release(fp); //数目减少171 printf("thread 1 exit.\n");72 pthread_exit((void*)1);73 }74 void * thread_func2(void *arg)75 {76 struct foo *fp = (struct foo*)arg;77 printf("thread 2 start.\n");78 foo_add(fp); //数目增加179 foo_add(fp);80 printf("thread 2 exit.\n");81 pthread_exit((void*)2);82 }
执行结果如下:
从结果可以看出:程序执行两次结果是不一样的,随即性比较强。线程1和线程2执行顺序是不确定的。需要带线程1和线程2进行同步控制。
(1)互斥量
mutex是一种简单的加锁的方法来控制对共享资源的访问。在同一时刻只能有一个线程掌握某个互斥上的锁,拥有上锁状态的线程能够对共享资源进行访问。若其他线程希望上锁一个已经被上了互斥锁的资源,则该线程挂起,直到上锁的线程释放互斥锁为止。互斥量类型为pthread_mutex_t。互斥量操作函数如下:
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutex_attr_t *mutexattr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_lock(pthread_mutex_t *mutex); //对互斥量进行加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex); //尝试对互斥量进行加锁
int pthread_mutex_unlock(pthread_mutex_t *mutex); //对互斥量进行解锁
写个程序练习一个互斥量,对以上程序添加互斥量,创建两个线程操作一个数据结构,修改公共的数据。程序如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <errno.h> 5 #include <sys/types.h> 6 #include <pthread.h> 7 8 struct foo 9 {10 int f_count;11 pthread_mutex_t f_lock; //互斥量12 };13 14 struct foo* foo_alloc(void);15 void foo_add(struct foo *fp);16 void foo_release(struct foo *fp);17 18 void * thread_func1(void *arg);19 void * thread_func2(void *arg);20 int main()21 {22 pthread_t pid1,pid2;23 int err;24 void *pret;25 struct foo *fobj;26 fobj = foo_alloc();27 err = pthread_create(&pid1,NULL,thread_func1,(void*)fobj);28 if(err != 0)29 {30 perror("pthread_create() error");31 exit(-1);32 }33 err = pthread_create(&pid2,NULL,thread_func2,(void*)fobj);34 if(err != 0)35 {36 perror("pthread_create() error");37 exit(-1);38 }39 pthread_join(pid1,&pret);40 printf("thread 1 exit code is: %d\n",(int)pret);41 pthread_join(pid2,&pret);42 printf("thread 2 exit code is: %d\n",(int)pret);43 exit(0);44 }45 struct foo* foo_alloc(void)46 {47 struct foo *fobj;48 fobj = (struct foo*)malloc(sizeof(struct foo));49 if(fobj != NULL)50 {51 fobj->f_count = 0;52 //初始化互斥量53 if (pthread_mutex_init(&fobj->f_lock,NULL) != 0)54 {55 free(fobj);56 return NULL;57 }58 }59 return fobj;60 }61 void foo_add(struct foo *fp)62 {63 pthread_mutex_lock(&fp->f_lock); //加锁64 fp->f_count++;65 printf("f_count = %d\n",fp->f_count);66 pthread_mutex_unlock(&fp->f_lock); //解锁67 } 68 void foo_release(struct foo *fp)69 {70 pthread_mutex_lock(&fp->f_lock); //加锁71 fp->f_count--;72 printf("f_count = %d\n",fp->f_count);73 if(fp->f_count == 0)74 {75 pthread_mutex_unlock(&fp->f_lock); //解锁76 pthread_mutex_destroy(&fp->f_lock); //是否锁77 free(fp);78 }79 else80 pthread_mutex_unlock(&fp->f_lock); //解锁81 }82 void * thread_func1(void *arg)83 {84 struct foo *fp = (struct foo*)arg;85 printf("thread 1 start.\n");86 foo_release(fp);87 printf("thread 1 exit.\n");88 pthread_exit((void*)1);89 }90 void * thread_func2(void *arg)91 {92 struct foo *fp = (struct foo*)arg;93 printf("thread 2 start.\n");94 foo_add(fp);95 foo_add(fp);96 printf("thread 2 exit.\n");97 pthread_exit((void*)2);98 }
程序执行结果如下:
从结果可以看出,程序执行多次结果相同,线程1和线程2同步操作。
(2)读写锁(共享锁)
读写锁可以使读操作比互斥量有更高的并行性,互斥量要么是锁住状态要么是不加锁状态,而且一次只有一个线程可以对其加锁。读写锁有三种状态:读模式下加锁状态、写模式下加锁状态、不加锁状态。一次只有一个线程可以占有写模式读写锁,而多个线程可以同时占有度模式的读写锁。当读操作较多,写操作较少时,可使用读写锁提高线程读并发性。读写锁数据类型为pthread_rwlock_t,操作函数如下:
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_mutex_t *mutex);
int pthread_rwlock_trywrlock(pthread_mutex_t *mutex);
写个程序练习读写锁,创建三个线程,两个线程读操作,一个线程写操作。程序如下:
程序执行结果如下:
(3)条件变量
条件变量给多个线程提供了个会合的机会,条件变量与互斥量一起使用,允许线程以无竞争的方式等待特定的条件发生,条件本身是由互斥量保护。线程在改变条件状态前必须先锁住互斥量,条件变量允许线程等待特定条件发生。条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线承间的同步。条件变量类型为pthread_cond_t,使用前必须进行初始化,操作函数如下:
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const structtimespec *restrict abstime);
int pthread_cond_broadcast(pthread_cond_t *cond); //唤醒等待该条件的所有线程
int pthread_cond_signal(pthread_cond_t *cond); //唤醒等待该条件的某个线程
写个程序练习条件变量,程序中创建两个新线程1和2,线程1使数目增加,线程2使数目减少。只有当数目不为0时,减少才能进行,为0时候需要等待。程序如下:
程序执行结果如下:
<<APUE>> 线程