一个进程在同一时刻只能做一件事情,线程可以把程序设计成在同一时刻能够做多件事情,每个线程处理各自独立的任务。线程包括了表示进程内执行环境必需的信息,包括进程中标识线程的线程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。线程创建时不能保证哪个线程会先运行。写个程序创建一个线程,输出线程标识符,程序如下:
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 pthread_t ntid;
9 void printpthreadinfo(const char* str);
10 void* thread_func(void *arg);
11
12 int main()
13 {
14 int err;
15 //创建一个新线程
16 err = pthread_create(&ntid,NULL,thread_func,NULL);
17 if(err != 0)
18 {
19 perror("pthread_create()");
20 exit(-1);
21 }
22 printpthreadinfo("main thread: ");
23 sleep(2); //给新线程时间执行
24 return 0;
25 }
26 void printpthreadinfo(const char* str)
27 {
28 pid_t pid;
29 pthread_t tid;
30 pid = getpid();
31 tid = pthread_self();
32 printf("%s pid %u tid %u (0x%x)\\n",str,(unsigned int)pid,(unsigned int)tid,(unsigned int)tid);
33 }
34 void* thread_func(void *arg)
35 {
36 printpthreadinfo("new thread: ");
37 return ((void*) 0);
38 }
由于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 //创建新线程1
18 err = pthread_create(&tid1,NULL,thread_func1,NULL);
19 if(err != 0)
20 {
21 perror("pthread_create() error");
22 exit(-1);
23 }
24 //创建新线程2
25 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 #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 static void cleanup(void *arg); //线程清理函数
9 void* thread_func1(void *arg);
10 void* thread_func2(void *arg);
11
12 int main()
13 {
14 pthread_t tid1;
15 pthread_t tid2;
16 int err;
17 void *tret;
18 //创建线程1
19 err = pthread_create(&tid1,NULL,thread_func1,(void*)1);
20 if(err != 0)
21 {
22 perror("pthread_create() error");
23 exit(-1);
24 }
25 //创建线程2
26 err = pthread_create(&tid2,NULL,thread_func2,(void*)1);
27 if(err != 0)
28 {
29 perror("pthread_create() error");
30 exit(-1);
31 }
32 //等待线程1结束
33 err = pthread_join(tid1,&tret);
34 if(err != 0)
35 {
36 perror("pthread_join error");
37 exit(-1);
38 }
39 printf("thread 1 exit code %d\\n",(int)tret);
40 //等待线程2结束
41 err = pthread_join(tid2,&tret);
42 if(err != 0)
43 {
44 perror("pthread_join error");
45 exit(-1);
46 }
47 printf("thread 2 exit code %d\\n",(int)tret);
48 exit(0);
49 }
50
51 static void cleanup(void *arg)
52 {
53 printf("cleanup: %s\\n",(char*)arg);
54 }
55 void* thread_func1(void *arg)
56 {
57 printf("Thread 1 start.\\n");
58 pthread_cleanup_push(cleanup,"thread 1 first handler");
59 pthread_cleanup_push(cleanup,"thread 1 second handler");
60 printf("thread 1 push complete.\\n");
61 if(arg)
62 return ((void*)1); //返回终止,将不会调用清理处理程序
63 pthread_cleanup_pop(0);
64 pthread_cleanup_pop(0);
65 return ((void*)1);
66 }
67 void* thread_func2(void *arg)
68 {
69 printf("Thread 2 start.\\n");
70 pthread_cleanup_push(cleanup,"thread 2 first handler");
71 pthread_cleanup_push(cleanup,"thread 2 second handler");
72 printf("thread 2 push complete.\\n");
73 if(arg)
74 pthread_exit((void*)2); //会调用清理处理程序
75 pthread_cleanup_pop(0);
76 pthread_cleanup_pop(0);
77 pthread_exit((void*)2);
78 }
程序执行结果如下:
从结果可以看出线程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,参数为fobj
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 ////创建新线程2,函数地址为thread_fun2,参数为fobj
34 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); //数目减少1
71 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); //数目增加1
79 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 Unix环境高级编程线程控制