多线程编程

线程是一个进程内的基本调度单位,也可以称为轻量级进程。线程是在共享内存空间中并发的多道执行路径,它们共享一个进程的资源,如文件描述和信号处理。因此,大大减少了上下文切换的开销。同进程一样,线程也将相关的变量值放在线程控制表内。一个进程可以有多个线程,也就是有多个线程控制表及堆栈寄存器,但却共享一个用户地址空间。要注意的是,由于线程共享了进程的资源和地址空间,因此,任何线程对系统资源的操作都会给其他线程带来影响,因此,多线程中的同步就是非常重要的问题了。

线程按照其调度者可以分为用户级线程和核心级线程两种。

用户级线程主要解决的是上下文切换的问题,它的调度算法和调度过程全部由用户自行选择决定,在运行时不需要特定的内核支持。在这里,操作系统往往会提供一个用户空间的
线程库,该线程库提供了线程的创建、调度、撤销等功能,而内核仍然仅对进程进行管理。如果一个进程中的某一个线程调用了一个阻塞的系统调用,那么该进程包括该进程中的其他
所有线程也同时被阻塞。这种用户级线程的主要缺点是在一个进程中的多个线程的调度中无法发挥多处理器的优势。

核心级线程这种线程允许不同进程中的线程按照同一相对优先调度方法进行调度,这样就可以发挥多处理器的并发优势。现在大多数系统都采用用户级线程与核心级线程并存的方法。一个用户级线程可以对应一个或几个核心级线程,也就是“一对一”或“多对一”模型。这样既可满足多处理机系统的需要,也可以最大限度地减少调度开销。

线程创建和退出

创建线程实际上就是确定调用该线程函数的入口点,通常使用的函数是pthread_create。在线程创建以后,就开始运行相关的线程函数,在该函数运行完之后,该线程也就退出了,这也是线程退出一种方法。另一种退出线程的方法是使用函数pthread_exit,这是线程的主动行为。要注意的是,在使用线程函数时,不能随意使用exit退出函数进行出错处理,由于exit的作用是使调用进程终止,往往一个进程包含多个线程,因此,在使用exit之后,该进程中的所有线程都终止了。因此,在线程中就可以使用pthread_exit 来代替进程中的exit。由于一个进程中的多个线程是共享数据段的,因此通常在线程退出之后,退出线程所占用的资源并不会随着线程的终止而得到释放。正如进程之间可以用wait()系统调用来同步终止并释放资源一样,线程之间也有类似机制,那就是pthread_join()函数。pthread_join可以用于将当前线程挂起,等待线程的结束。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源就被收回。

修改线程属性

系统默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。
· 绑定属性
Linux 中采用“一对一”的线程机制,也就是一个用户线程对应一个内核线程。绑定属性就是指一个用户线程固定地分配给一个内核线程,因为CPU时间片的调度是面向内核线程(也就是轻量级进程)的,因此具有绑定属性的线程可以保证在需要的时候总有一个内核线程与之对应。而与之相对的非绑定属性就是指用户线程和内核线程的关系不
是始终固定的,而是由系统来控制分配的。
· 分离属性
分离属性是用来决定一个线程以什么样的方式来终止自己。在非分离情况下,当一个线程结束时,它所占用的系统资源并没有被释放,也就是没有真正的终止。只有当pthread_join()函数返回时,创建的线程才能释放自己占用的系统资源。而在分离属性情况下,一个线程结束时立即释放它所占有的系统资源。这里要注意的一点是,如果设置一个线程的分离属性,而这个线程运行又非常快,那么它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这时调用pthread_create的线程就得到了错误的线程号。

这些属性的设置都是通过一定的函数来完成的,通常首先调用pthread_attr_init函数进行初始化,之后再调用相应的属性设置函数。设置绑定属性的函数为pthread_attr_setscope,设置线程分离属性的函数为pthread_attr_setdetachstate,设置线程优先级的相关函数为pthread_attr_getschedparam(获取线程优先级)和pthread_attr_setschedparam(设置线程优先级)。在设置完这些属性后,就可以调用pthread_create函数来创建线程了。

 1 /*pthread.c*/
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4 #include <pthread.h>
 5 #include <time.h>
 6 /*线程一*/
 7 void thread1(void)
 8 {
 9     int i=0;
10     for(i=0;i<6;i++)
11     {
12         printf("This is a pthread1.\n");
13         if(i==2)
14             pthread_exit(0);
15         sleep(1);
16     }
17 }
18 /*线程二*/
19 void thread2(void)
20 {
21     int i;
22     while(1)
23     {
24         for(i=0;i<3;i++)
25             printf("This is a pthread2.\n");
26         sleep(1);
27     }
28     pthread_exit(0);
29 }
30 int main(void)
31 {
32     pthread_t id1,id2;
33     int i,ret;
34     pthread_attr_t attr;
35     /*初始化线程*/
36     pthread_attr_init(&attr);
37     /*设置线程绑定属性*/
38     pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
39     /*设置线程分离属性*/
40     pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
41     /*创建线程1*/
42     ret=pthread_create(&id1,&attr,(void *) thread1,NULL);
43     if(ret!=0){
44         printf ("Create pthread error!\n");
45         exit (1);
46     }
47     /*创建线程2*/
48     ret=pthread_create(&id2,NULL,(void *) thread2,NULL);
49     if(ret!=0){
50         printf ("Create pthread error!\n");
51         exit (1);
52     }
53     pthread_join(id2,NULL);
54     return (0);
55 }

由于pthread库不是Linux系统默认的库,连接时需要使用库libpthread.a,所以在使用pthread_create创建线程时,在编译中要加-lpthread或-pthread参数。目前gcc 4.5.2中已经没有了关于 -lpthread的介绍了。所以以后的多线程编译应该用-pthread,而不是-lpthread。

在线程一运行前后使用“free”命令查看内存使用情况,可以看到,线程一在运行结束后就收回了系统资源,释放了内存。

 

线程访问控制

POSIX 中线程同步的方法,主要有互斥锁和信号量的方式。

1.mutex 互斥锁线程控制

在同一时刻只能有一个线程掌握某个互斥上的锁,拥有上锁状态的线程能够对共享资源进行操作。若其他线程希望上锁一个已经上锁了的互斥锁,则该线程就会挂起,直到上锁的线程释放掉互斥锁为止。

互斥锁的操作主要包括以下几个步骤。
· 互斥锁初始化:pthread_mutex_init
· 互斥锁上锁:pthread_mutex_lock
· 互斥锁判断上锁:pthread_mutex_trylock
· 互斥锁解锁:pthread_mutex_unlock
· 消除互斥锁:pthread_mutex_destroy

 1 /*mutex.c*/
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4 #include <unistd.h>
 5 #include <pthread.h>
 6 #include <errno.h>
 7 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //快速互斥锁
 8 int lock_var;
 9 time_t end_time;
10 void pthread1(void *arg)
11 {
12     int i;
13     while(time(NULL) < end_time)
14     {
15         /*互斥锁上锁*/
16         if(pthread_mutex_lock(&mutex)!=0){
17             perror("pthread_mutex_lock");
18         }
19         else
20             printf("pthread1:pthread1 lock the variable\n");
21         for(i=0;i<2;i++)
22         {
23             sleep(1);
24             lock_var++;
25         }
26         /*互斥锁解锁*/
27         if(pthread_mutex_unlock(&mutex)!=0){
28             perror("pthread_mutex_unlock");
29         }
30         else
31             printf("pthread1:pthread1 unlock the variable\n");
32         sleep(1);
33         }
34 }
35 void pthread2(void *arg)
36 {
37     int nolock=0;
38     int ret;
39     while(time(NULL) < end_time)
40     {
41         /*测试互斥锁*/
42         ret=pthread_mutex_trylock(&mutex);
43         if(ret==EBUSY)
44             printf("pthread2:the variable is locked by pthread1\n");
45         else
46         {
47             if(ret!=0){
48                 perror("pthread_mutex_trylock");
49                 exit(1);
50             }
51             else
52                 printf("pthread2:pthread2 got lock.The variable is %d\n",lock_var);
53             /*互斥锁解锁*/
54             if(pthread_mutex_unlock(&mutex)!=0){
55                 perror("pthread_mutex_unlock");
56             }
57             else
58                 printf("pthread2:pthread2 unlock the variable\n");
59         }
60         sleep(3);
61     }
62 }
63 int main(int argc, char *argv[])
64 {
65     pthread_t id1,id2;
66     pthread_t mon_th_id;
67     int ret;
68     end_time = time(NULL)+10;
69     /*互斥锁初始化*/
70     pthread_mutex_init(&mutex,NULL);
71     /*创建两个线程*/
72     ret=pthread_create(&id1,NULL,(void *)pthread1, NULL);
73     if(ret!=0)
74         perror("pthread cread1");
75     ret=pthread_create(&id2,NULL,(void *)pthread2, NULL);
76     if(ret!=0)
77         perror("pthread cread2");
78     pthread_join(id1,NULL);
79     pthread_join(id2,NULL);
80     exit(0);
81 }

2.信号量线程控制

PV原语是对整数计数器信号量sem的操作。一次P操作使sem减一,而一次V操作使sem 加一。进程(或线程)根据信号量的值来判断是否对公共资源具有访问权限。当信号量
sem 的值大于等于零时,该进程(或线程)具有公共资源的访问权限;相反,当信号量sem的值小于零时,该进程(或线程)就将阻塞直到信号量sem的值大于等于0 为止。
PV 原语主要用于进程或线程间的同步和互斥这两种典型情况。若用于互斥,几个进程(或线程)往往只设置一个信号量sem。当信号量用于同步操作时,往往会设置多个信号量。

· sem_init用于创建一个信号量,并能初始化它的值。
· sem_wait和sem_trywait相当于P操作,它们都能将信号量的值减一,两者的区别在于若信号量小于零时,sem_wait将会阻塞进程,而sem_trywait则会立即返回。
· sem_post相当于V操作,它将信号量的值加一同时发出信号唤醒等待的进程。
· sem_getvalue用于得到信号量的值。
· sem_destroy用于删除信号量。

只使用一个信号量:

 1 /*sem_mutex.c*/
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4 #include <unistd.h>
 5 #include <pthread.h>
 6 #include <errno.h>
 7 #include <sys/ipc.h>
 8 #include <semaphore.h>
 9 int lock_var=0;
10 time_t end_time;
11 sem_t sem;
12 void pthread1(void *arg);
13 void pthread2(void *arg);
14 void pthread1(void *arg)
15 {
16     int i;
17     while(time(NULL) < end_time)
18     {
19         /*信号量减一,P操作*/
20         sem_wait(&sem);
21         for(i=0;i<2;i++)
22         {
23             sleep(1);
24             lock_var++;
25             printf("lock_var=%d\n",lock_var);
26         }
27         printf("pthread1:lock_var=%d\n",lock_var);
28         /*信号量加一,V操作*/
29         sem_post(&sem);
30         sleep(1);
31     }
32 }
33 void pthread2(void *arg)
34 {
35     while(time(NULL) < end_time)
36     {
37         /*信号量减一,P操作*/
38         sem_wait(&sem);
39         printf("pthread2:pthread1 got lock;lock_var=%d\n",lock_var);
40         /*信号量加一,V操作*/
41         sem_post(&sem);
42         sleep(3);
43     }
44 }
45 int main(int argc, char *argv[])
46 {
47     pthread_t id1,id2;
48     pthread_t mon_th_id;
49     int ret;
50     end_time = time(NULL)+30;
51     /*初始化信号量为1*/
52     ret=sem_init(&sem,0,1);
53     if(ret!=0)
54     {
55         perror("sem_init");
56     }
57     /*创建两个线程*/
58     ret=pthread_create(&id1,NULL,(void *)pthread1, NULL);
59     if(ret!=0)
60         perror("pthread cread1");
61     ret=pthread_create(&id2,NULL,(void *)pthread2, NULL);
62     if(ret!=0)
63         perror("pthread cread2");
64     pthread_join(id1,NULL);
65     pthread_join(id2,NULL);
66     exit(0);
67 }

 

两个信号量来实现两个线程间的同步:

 1 /*sem_syn.c*/
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4 #include <unistd.h>
 5 #include <pthread.h>
 6 #include <errno.h>
 7 #include <sys/ipc.h>
 8 #include <semaphore.h>
 9 int lock_var=0;
10 time_t end_time;
11 sem_t sem1,sem2;
12 void pthread1(void *arg);
13 void pthread2(void *arg);
14 void pthread1(void *arg)
15 {
16     int i;
17     while(time(NULL) < end_time)
18     {
19         /*P操作信号量2*/
20         sem_wait(&sem2);
21         for(i=0;i<2;i++)
22         {
23             sleep(1);
24             lock_var++;
25             printf("lock_var=%d\n",lock_var);
26         }
27         printf("pthread1:lock_var=%d\n",lock_var);
28         /*V操作信号量1*/
29         sem_post(&sem1);
30         sleep(1);
31     }
32 }
33 void pthread2(void *arg)
34 {
35     while(time(NULL) < end_time)
36     {
37         /*P操作信号量1*/
38         sem_wait(&sem1);
39         printf("pthread2:pthread1 got lock;lock_var=%d\n",lock_var);
40         /*V操作信号量2*/
41         sem_post(&sem2);
42         sleep(3);
43     }
44 }
45 int main(int argc, char *argv[])
46 {
47     pthread_t id1,id2;
48     int ret;
49     end_time = time(NULL)+30;
50     /*初始化两个信号量,一个信号量为1,一个信号量为0*/
51     ret=sem_init(&sem1,0,1);
52     ret=sem_init(&sem2,0,0);
53     if(ret!=0)
54     {
55         perror("sem_init");
56     }
57     /*创建两个线程*/
58     ret=pthread_create(&id1,NULL,(void *)pthread1, NULL);
59     if(ret!=0)
60         perror("pthread cread1");
61     ret=pthread_create(&id2,NULL,(void *)pthread2, NULL);
62     if(ret!=0)
63         perror("pthread cread2");
64     pthread_join(id1,NULL);
65     pthread_join(id2,NULL);
66     exit(0);
67 }

实现了先运行线程二,再运行线程一。

posted @ 2015-03-01 12:19  ht-beyond  阅读(280)  评论(0编辑  收藏  举报