<<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     //创建新线程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的清理处理程序没有被调用,线程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     {
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     else
80          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时候需要等待。程序如下:

程序执行结果如下:

时间: 2024-10-11 21:48:59

<<APUE>> 线程的相关文章

各科基础详实

一. Java基础部分 1. JAVA的基本数据类型有哪些 ?  String 是不是基本数据类型 ? 2. 一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制? 3. Java有没有goto? 7 4. 说说&和&&的区别. 7 5. 在JAVA中如何跳出当前的多重嵌套循环? 7 6. switch语句能否作用在byte上,能否作用在long上,能否作用在String上? 8 7. short s1 = 1; s1 = s1 + 1;有什么

Java四种线程池newCachedThreadPool,newFixedThreadPool,newScheduledThreadPool,newSingleThreadExecutor

介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用.本文是基础篇,后面会分享下线程池一些高级功能. 1.new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? Java new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub } }).start(); 1 2 3 4 5 6 7 new Thread(new

201709018工作日记--线程状态的转换

先来张图: 线程在一定条件下,状态会发生变化: 1.新建状态(New):新创建了一个线程对象 2.就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法.该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权. 3.运行状态(Running):就绪状态的线程获取了CPU,执行程序代码. 4.阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行.直到线程进入就绪状态,才有机会转到运行状态.阻塞的情况分三种:   (一).等

POSIX 线程详解(经典必看)

总共三部分: 第一部分:POSIX 线程详解                                   Daniel Robbins ([email protected]), 总裁/CEO, Gentoo Technologies, Inc.  2000 年 7 月 01 日 第二部分:通用线程:POSIX 线程详解,第 2部分       Daniel Robbins ([email protected]), 总裁/CEO, Gentoo Technologies, Inc.  20

线程的控制和线程池

一.WaitHandle: ”.Net 中提供了一些线程间更自由通讯的工具,他们提供了通过"信号"进行通讯的机制 可以通过ManualResetEvent,AutoResetEvent(他是在开门并且一个 WaitOne 通过后自动关门)来进行线程间的通讯 waitOne:    等待开门 Set:           开门 Reset:       关门 static void Main(string[] args) { ManualResetEvent mre = new Manu

内存池、进程池、线程池

首先介绍一个概念"池化技术 ".池化技术 一言以蔽之就是:提前保存大量的资源,以备不时之需以及重复使用. 池化技术应用广泛,如内存池,线程池,连接池等等.内存池相关的内容,建议看看Apache.Nginx等开源web服务器的内存池实现. 起因:由于在实际应用当中,分配内存.创建进程.线程都会设计到一些系统调用,系统调用需要导致程序从用户态切换到内核态,是非常耗时的操作.           因此,当程序中需要频繁的进行内存申请释放,进程.线程创建销毁等操作时,通常会使用内存池.进程池.

线程高级

例题,哲学家用餐: 在一张餐桌上坐着五个哲学家,但是却只有五根筷子,每个哲学家只有一根筷子,所以当一个哲学家要夹菜的时候需要用他旁边那个哲学家的筷子,被拿走筷子的哲学家则只能等待那个哲学家吃完放下筷子他才能夹菜. 示意图:  设计思路: 首先编写一个筷子类,每根筷子都是一个对象,这个类里有拿起.放下两个方法:当一个哲学家线程来调用拿起方法时,下一个哲学家线程就要进入等待状态,然后这个哲学家线程调用放下方法后,就激活那个等待中的哲学家线程,以此循环,轮流使用筷子. 代码示例:  接着设计哲学家类,

缓冲池,线程池,连接池

SSH:[email protected]:unbelievableme/object-pool.git   HTTPS:https://github.com/unbelievableme/object-pool.git 缓冲池 设计要点:包含三个队列:空缓冲队列(emq),装满输入数据的输入的队列(inq),装满输出数据的输出队列(outq),输入程序包括收容输入(hin),提取输入(sin),输出程序包括收容输出(hout)和提取输出(sout). 注意点:输入程序和输出程序会对缓冲区并发访

能够在子线程绘画的View SurfaceView

转载请注明出处:王亟亟的大牛之路 近期两天都没有写文章,一方面是自己在看书.一方面不知道写什么,本来昨天想写Glide或者RxAndroid的东西结果公司的"狗屎"网怎么都刷不好Gradle我也是无语了(FQ也没用).准备今天背着笔记本 回家搞.真是服了.. 抱怨的话不说了,来看下这一篇要讲的主角 SurfaceView,关于SurfaceView的文章事实上在别的一些网站上也有,由于我之前没写过,所以也就一直没整这部分的内容(别人写的好坏反正找好的点自己吸收吧,嘿嘿) 问题:Surf

java线程安全问题之静态变量、实例变量、局部变量

Java多线程编程中,存在很多线程安全问题,至于什么是线程安全呢,给出一个通俗易懂的概念还是蛮难的,如同<java并发编程实践>中所说: 写道 给线程安全下定义比较困难.存在很多种定义,如:"一个类在可以被多个线程安全调用时就是线程安全的". 此处不赘述了,首先给出静态变量.实例变量.局部变量在多线程环境下的线程安全问题结论,然后用示例验证,请大家擦亮眼睛,有错必究,否则误人子弟! 静态变量:线程非安全. 静态变量即类变量,位于方法区,为所有对象共享,共享一份内存,一旦静态