Download presentation
Presentation is loading. Please wait.
Published by隆炼 江 Modified 8年之前
1
© 2005 博创科技 北 京 博 创 兴 业 科 技 有 限 公 司 BEIJNG UNIVERSAL PIONEERING TECHNOLOGY Co., LTD 博创科技 嵌入互动 第四讲 Linux 多线程编程 北京博创兴业科技有限公司
2
博创科技 嵌入互动 © 2005 博创科技 Linux 多线程编程 主讲人:王君 wju_uptech@126.com
3
博创科技 嵌入互动 © 2006 博创科技 进程与线程 使用多线程的理由之一是和进程相比,它是一种非常 " 节俭 " 的 多任务操作方式。 在 Linux 系统下,启动一个新的进程必须分配给它独立的地址 空间,建立众多的数据表来维护它的代码段、堆栈段和数据 段,这是一种 " 昂贵 " 的多任务工作方式。 而运行于一个进程中的多个线程,它们彼此之间使用相同的 地址空间,共享大部分数据,启动一个线程所花费的空间远 远小于启动一个进程所花费的空间,而且,线程间彼此切换 所需的时间也远远小于进程间切换所需要的时间。
4
博创科技 嵌入互动 © 2006 博创科技 进程与线程(二) 使用多线程的理由之二是线程间方便的通信机制。 不同的进程具有独立的数据空间,要进行数据的传 递只能通过通信的方式进行 同一进程下的线程之间共享数据空间,所以一个线 程的数据可以直接为其它线程所用
5
博创科技 嵌入互动 © 2006 博创科技 多线程程序的优点 提高应用程序响应。当一个操作耗时很长时,整个 系统都会等待这个操作,多线程技术会将耗时长的 操作( time consuming )置于一个新的线程。 使多 CPU 系统更加有效。操作系统会保证当线程数 不大于 CPU 数目时,不同的线程运行于不同的 CPU 上。 改善程序结构。一个既长又复杂的进程可以考虑分 为多个线程,成为几个独立或半独立的运行部分。
6
博创科技 嵌入互动 © 2006 博创科技 多线程编程起步 编写 Linux 下的多线程程序,需要使用头文件 pthread.h ,连接时需要使用库 libpthread.a 多线程程序实例 /* example.c*/ #include #include void thread(void) { int i; for(i=0;i<3;i++) printf("This is a pthread.\n"); }
7
博创科技 嵌入互动 © 2006 博创科技 int main(void) { pthread_t id; int i,ret; ret=pthread_create(&id,NULL,(void *) thread,NULL); if(ret!=0){ printf ("Create pthread error!\n"); exit (1); } for(i=0;i<3;i++) printf("This is the main process.\n"); pthread_join(id,NULL); return (0); }
8
博创科技 嵌入互动 © 2006 博创科技 执行: gcc example.c -lpthread -o example -l 参数用于指定编译时要用到的库 运行生成的 example 每次运行的结果可能不同,这是因为两个线程在争 夺 CPU 资源
9
博创科技 嵌入互动 © 2006 博创科技 线程标识符 pthread_t pthread_t 在头文件 /usr/include/bits/pthreadtypes.h 中定义: typedef unsigned long int pthread_t; 用来标识一个线程
10
博创科技 嵌入互动 © 2006 博创科技 主要 API 函数介绍 LIBC 中的 pthread 库提供了大量的 API 函数 $ cd /usr/lib
11
博创科技 嵌入互动 © 2006 博创科技 线程创建函数 函数原型: int pthread_create (pthread_t * thread_id , __const pthread_attr_t * __attr, void *(*__start_routine) (void *),void *__restrict __arg) 第一个参数为指向线程标识符的指针 第二个参数用来设置线程属性 第三个参数是线程运行函数的起始地址 最后一个参数是运行函数的参数 函数 thread 不需要参数,所以最后一个参数设为空指针。 第二个参数也设为空指针,这样将生成默认属性的线程。
12
博创科技 嵌入互动 © 2006 博创科技 当创建线程成功时,函数返回 0 ,若不为 0 则说明创 建线程失败,常见的错误返回代码为 EAGAIN 和 EINVAL 。前者表示系统限制创建新的线程,例如线 程数目过多了;后者表示第二个参数代表的线程属 性值非法。 创建线程成功后,新创建的线程则运行参数三和参 数四确定的函数,原来的线程则继续运行下一行代 码。
13
博创科技 嵌入互动 © 2006 博创科技 pthread_join 函数 用来等待一个线程的结束。 函数原型: int pthread_join (pthread_t __th, void **__thread_return) 第一个参数为被等待的线程标识符 。 第二个参数为一个用户定义的指针,用来存储被等 待线程返回值。 这个函数是一个线程阻塞的函数,调用它的函数将 一直等待到被等待的线程结束为止,当函数返回时, 被等待线程的资源被收回。
14
博创科技 嵌入互动 © 2006 博创科技 pthread_exit 函数 一个线程的结束有两种途径,一种是象我们上面的 例子,函数结束了,调用它的线程也就结束了;另 一种方式是通过函数 pthread_exit 实现 。 函数原型: void pthread_exit (void *__retval ) 唯一的参数是函数的返回代码 。如果 pthread_join 中 的第二个参数 thread_return 不是 NULL ,这个值将被 传递给 thread_return 。 需要注意的是:一个线程不能被多个线程等待,否 则第一个接收到信号的线程成功返回,其余调用 pthread_join 的线程则返回错误代码 ESRCH 。
15
博创科技 嵌入互动 © 2006 博创科技 线程的属性 使用 pthread_create 函数创建线程时,线程参数一般 都为默认值,即将第二个参数设为 NULL ,对大多数 程序来说,使用默认属性就够了 属性结构为 pthread_attr_t ,它在头文件 /usr/include/pthread.h 中定义,属性值不能直接设置, 须使用相关函数进行操作,初始化的函数为 pthread_attr_init ,这个函数必须在 pthread_create 函 数之前调用。
16
博创科技 嵌入互动 © 2006 博创科技 线程的优先级 线程的优先级是线程的常用属性, 它存放在结构 sched_param 中。用函数 pthread_attr_getschedparam 和函数 pthread_attr_setschedparam 进行存放,一般是先取 优先级,对取得的值修改后再存放回去。 下面是一个简单的例子:
17
博创科技 嵌入互动 © 2006 博创科技 #include #include pthread_attr_t attr; pthread_t tid; sched_param param; int newprio=20; pthread_attr_init(&attr); pthread_attr_getschedparam(&attr, ¶m); param.sched_priority=newprio; pthread_attr_setschedparam(&attr, ¶m); pthread_create(&tid, &attr, (void *)myfunction, myarg);
18
博创科技 嵌入互动 © 2006 博创科技 互斥锁 互斥锁用来保证一段时间内只有一个线程在执行一 段代码 看下面一段代码。这是一个读 / 写程序,它们公用一 个缓冲区,并且假定一个缓冲区只能保存一条信息。 即缓冲区只有两个状态:有信息或没有信息。
19
博创科技 嵌入互动 © 2006 博创科技 void reader_function ( void ); void writer_function ( void ); char buffer; int buffer_has_item=0; pthread_mutex_t mutex; struct timespec delay;
20
博创科技 嵌入互动 © 2006 博创科技 void main ( void ){ pthread_t reader; /* 定义延迟时间 */ delay.tv_sec = 2; delay.tv_nec = 0; /* 用默认属性初始化一个互斥锁对象 */ pthread_mutex_init (&mutex,NULL); pthread_create(&reader, pthread_attr_default, (void *)&reader_function), NULL); writer_function( ); }
21
博创科技 嵌入互动 © 2006 博创科技 void writer_function (void){ while(1){ /* 锁定互斥锁 */ pthread_mutex_lock (&mutex); if (buffer_has_item==0){ buffer=make_new_item( ); buffer_has_item=1; } /* 打开互斥锁 */ pthread_mutex_unlock(&mutex); pthread_delay_np(&delay); } }
22
博创科技 嵌入互动 © 2006 博创科技 void reader_function(void){ while(1){ pthread_mutex_lock(&mutex); if(buffer_has_item==1){ consume_item(buffer); buffer_has_item=0; } pthread_mutex_unlock(&mutex); pthread_delay_np(&delay); } }
23
博创科技 嵌入互动 © 2006 博创科技 声明互斥锁变量 pthread_mutex_t mutex 函数 pthread_mutex_init 用来生成一个互斥锁。 NULL 参数表明使用默认属性。
24
博创科技 嵌入互动 © 2006 博创科技 pthread_mutex_lock 声明开始用互斥锁上锁,此后的 代码直至调用 pthread_mutex_unlock 为止,均被上 锁,即同一时间只能被一个线程调用执行。当一个 线程执行到 pthread_mutex_lock 处时,如果该锁此时 被另一个线程使用,那此线程被阻塞,即程序将等 待到另一个线程释放此互斥锁。在上面的例子中, 我们使用了 pthread_delay_np 函数,让线程睡眠一段 时间,就是为了防止一个线程始终占据此函数。 。
25
博创科技 嵌入互动 © 2006 博创科技 条件变量 使用互斥锁可实现线程间数据的共享和通信,互斥 锁一个明显的缺点是它只有两种状态:锁定和非锁 定。而条件变量通过允许线程阻塞和等待另一个线 程发送信号的方法弥补了互斥锁的不足,它常和互 斥锁一起使用。使用时,条件变量被用来阻塞一个 线程,当条件不满足时,线程往往解开相应的互斥 锁并等待条件发生变化。一旦其它的某个线程改变 了条件变量,相应的条件变量唤醒一个或多个正被 此条件变量阻塞的线程。这些线程将重新锁定互斥 锁并重新测试条件是否满足。
26
博创科技 嵌入互动 © 2006 博创科技 pthread_cond_init 函数 条件变量的结构为 pthread_cond_t ,函数 pthread_cond_init ()被用来初始化一个条件变量。 它的原型为: int pthread_cond_init (pthread_cond_t * cond, __const pthread_condattr_t * cond_attr) 其中 cond 是一个指向结构 pthread_cond_t 的指针 cond_attr 是一个指向结构 pthread_condattr_t 的指针。 结构 pthread_condattr_t 是条件变量的属性结构
27
博创科技 嵌入互动 © 2006 博创科技 注意:条件变量只有在未被使用时才能重新初始化 或被释放。 释放一个条件变量的函数为 pthread_cond_ destroy ( pthread_cond_t cond )。
28
博创科技 嵌入互动 © 2006 博创科技 pthread_cond_wait 函数 使线程阻塞在一个条件变量上。 函数原型: extern int pthread_cond_wait (pthread_cond_t *__restrict__cond, pthread_mutex_t *__restrict __mutex) 线程解开 mutex 指向的锁并被条件变量 cond 阻塞。线程可以 被函数 pthread_cond_signal 和函数 pthread_cond_broadcast 唤醒 但是要注意的是,条件变量只是起阻塞和唤醒线程的作用, 具体的判断条件还需用户给出,例如一个变量是否为 0 等等, 线程被唤醒后,它将重新检查判断条件是否满足,如果还不 满足,一般说来线程应该仍阻塞在这里,等待被下一次唤醒。 这个过程一般用 while 语句实现。
29
博创科技 嵌入互动 © 2006 博创科技 pthread_cond_timedwait 函数 也可以用来阻塞线程 函数原型: extern int pthread_cond_timedwait (pthread_cond_t *__cond, pthread_mutex_t *__mutex, __const struct timespec *__abstime) 它比函数 pthread_cond_wait ()多了一个时间参数, 经历 abstime 段时间后,即使条件变量不满足,阻塞 也被解除。
30
博创科技 嵌入互动 © 2006 博创科技 pthread_cond_signal 函数 用来释放被阻塞在条件变量 cond 上的一个线程。 函数原型: extern int pthread_cond_signal (pthread_cond_t *__cond) 多个线程阻塞在此条件变量上时,哪一个线程被唤 醒是由线程的调度策略所决定的。
31
博创科技 嵌入互动 © 2006 博创科技 使用函数 pthread_cond_wait ()和函数 pthread_cond_signal ()的一个简单的例子 pthread_mutex_t count_lock; pthread_cond_t count_nonzero; unsigned count; decrement_count () { pthread_mutex_lock (&count_lock); while(count==0) pthread_cond_wait( &count_nonzero, &count_lock); count=count -1; pthread_mutex_unlock (&count_lock); }
32
博创科技 嵌入互动 © 2006 博创科技 increment_count(){ pthread_mutex_lock(&count_lock); if(count==0) pthread_cond_signal(&count_nonzero); count=count+1; pthread_mutex_unlock(&count_lock); } count 值为 0 时, decrement 函数 pthread_cond_wait 处被阻塞,并打开互斥锁 count_lock 。此时,当调用 到函数 increment_count 时, pthread_cond_signal () 函数改变条件变量,告知 decrement_count ()停止 阻塞。
33
博创科技 嵌入互动 © 2006 博创科技 下面我们分析一下著名的生产者-消费者问题模型 的实现,主程序中分别启动生产者线程和消费者线 程。生产者线程不断顺序地将 0 到 1000 的数字写入共 享的循环缓冲区,同时消费者线程不断地从共享的 循环缓冲区读取数据。
34
博创科技 嵌入互动 © 2006 博创科技 产者写入缓冲区和消费者从缓冲区读数的具体流程: 生产者首先要获得互斥锁,并且判断写指针 +1 后是否等 于读指针,如果相等则进入等待状态,等候条件变 notfull ; 如果不等则向缓冲区中写一个整数,并且设置条件变量为 notempty ,最后释放互斥锁。消费者线程与生产者线程 类似 。
35
博创科技 嵌入互动 © 2006 博创科技
Similar presentations