Unix环境高级编程线程

Posted AlanTu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unix环境高级编程线程相关的知识,希望对你有一定的参考价值。

  一个进程在同一时刻只能做一件事情,线程可以把程序设计成在同一时刻能够做多件事情,每个线程处理各自独立的任务。线程包括了表示进程内执行环境必需的信息,包括进程中标识线程的线程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环境高级编程线程控制

Unix高级环境编程学习小结

Unix高级编程之进程环境

Unix环境高级编程书上的第一个例程如何运行(附代码)

unix高级环境编程看了能写项目吗

unix环境高级编程附录 B 通用代码