linux多线程设计1.docx
- 文档编号:10258780
- 上传时间:2023-02-09
- 格式:DOCX
- 页数:15
- 大小:24.50KB
linux多线程设计1.docx
《linux多线程设计1.docx》由会员分享,可在线阅读,更多相关《linux多线程设计1.docx(15页珍藏版)》请在冰豆网上搜索。
linux多线程设计1
1 Linux线程概述
1.1 线程概述
前面已经提到,进程是系统中程序执行和资源分配的基本单位。
每个进程都拥有自己的数据段、代码段和堆栈段,这就造成了进程在进行切换等操作时都需要有比较复杂的上下文切换等动作。
为了进一步减少处理机的空转时间,支持多处理器以及减少上下文切换开销,进程在演化中出现了另一个概念——线程。
它是进程内独立的一条运行路线,处理器调度的最小单元,也可以称为轻量级进程。
线程可以对进程的内存空间和资源进行访问,并与同一进程中的其他线程共享。
因此,线程的上下文切换的开销比创建进程小很多。
同进程一样,线程也将相关的执行状态和存储变量放在线程控制表内。
一个进程可以有多个线程,也就是有多个线程控制表及堆栈寄存器,但却共享一个用户地址空间。
要注意的是,由于线程共享了进程的资源和地址空间,因此,任何线程对系统资源的操作都会给其他线程带来影响。
由此可知,多线程中的同步是非常重要的问题。
在多线程系统中,进程与进程的关系如图1所示。
图1 进程与线程关系
1.2 线程机制的分类和特性
线程按照其调度者可以分为用户级线程和核心级线程两种。
(1)用户级线程。
用户级线程主要解决的是上下文切换的问题,它的调度算法和调度过程全部由用户自行选择决定,在运行时不需要特定的内核支持。
在这里,操作系统往往会提供一个用户空间的线程库,该线程库提供了线程的创建、调度和撤销等功能,而内核仍然仅对进程进行管理。
如果一个进程中的某一个线程调用了一个阻塞的系统调用函数,那么该进程包括该进程中的其他所有线程也同时被阻塞。
这种用户级线程的主要缺点是在一个进程中的多个线程的调度中无法发挥多处理器的优势。
(2)轻量级进程。
轻量级进程是内核支持的用户线程,是内核线程的一种抽象对象。
每个线程拥有一个或多个轻量级线程,而每个轻量级线程分别被绑定在一个内核线程上。
(3)内核线程。
这种线程允许不同进程中的线程按照同一相对优先调度方法进行调度,这样就可以发挥多处理器的并发优势。
现在大多数系统都采用用户级线程与核心级线程并存的方法。
一个用户级线程可以对应一个或几个核心级线程,也就是“一对一”或“多对一”模型。
这样既可满足多处理机系统的需要,也可以最大限度地减少调度开销。
使用线程机制大大加快上下文切换速度而且节省很多资源。
但是因为在用户态和内核态均要实现调度管理,所以会增加实现的复杂度和引起优先级翻转的可能性。
一个多线程程序的同步设计与调试也会增加程序实现的难度。
2 Linux线程编程
2.1 线程基本编程
这里要讲的线程相关操作都是用户空间中的线程的操作。
在Linux中,一般pthread线程库是一套通用的线程库,是由POSIX提出的,因此具有很好的可移植性。
(1)函数说明。
创建线程实际上就是确定调用该线程函数的入口点,这里通常使用的函数是pthread_create()。
在线程创建以后,就开始运行相关的线程函数,在该函数运行完之后,该线程也就退出了,这也是线程退出一种方法。
另一种退出线程的方法是使用函数pthread_exit(),这是线程的主动行为。
这里要注意的是,在使用线程函数时,不能随意使用exit()退出函数进行出错处理,由于exit()的作用是使调用进程终止,往往一个进程包含多个线程,因此,在使用exit()之后,该进程中的所有线程都终止了。
因此,在线程中就可以使用pthread_exit()来代替进程中的exit()。
由于一个进程中的多个线程是共享数据段的,因此通常在线程退出之后,退出线程所占用的资源并不会随着线程的终止而得到释放。
正如进程之间可以用wait()系统调用来同步终止并释放资源一样,线程之间也有类似机制,那就是pthread_join()函数。
pthread_join()可以用于将当前线程挂起来等待线程的结束。
这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源就被收回。
前面已提到线程调用pthread_exit()函数主动终止自身线程。
但是在很多线程应用中,经常会遇到在别的线程中要终止另一个线程的执行的问题。
此时调用pthread_cancel()函数实现这种功能,但在被取消的线程的内部需要调用pthread_setcancel()函数和pthread_setcanceltype()函数设置自己的取消状态,例如被取消的线程接收到另一个线程的取消请求之后,是接受还是忽略这个请求;如果接受,是立刻进行终止操作还是等待某个函数的调用等。
(2)函数格式。
表1列出了pthread_create()函数的语法要点。
表pthread_create()函数语法要点
所需头文件
#include
函数原型
intpthread_create((pthread_t*thread,pthread_attr_t*attr,
void*(*start_routine)(void*),void*arg))
函数传入值
thread:
线程标识符
attr:
线程属性设置(其具体设置参见2.3小节),通常取为NULL
start_routine:
线程函数的起始地址,是一个以指向void的指针作为参数和返回值的函数指针
arg:
传递给start_routine的参数
函数返回值
成功:
0
出错:
返回错误码
表2列出了pthread_exit()函数的语法要点。
表2pthread_exit()函数语法要点
所需头文件
#include
函数原型
voidpthread_exit(void*retval)
函数传入值
retval:
线程结束时的返回值,可由其他函数如pthread_join()来获取
表3列出了pthread_join()函数的语法要点。
表3pthread_join()函数语法要点
所需头文件
#include
函数原型
intpthread_join((pthread_tth,void**thread_return))
函数传入值
th:
等待线程的标识符
thread_return:
用户定义的指针,用来存储被等待线程结束时的返回值(不为NULL时)
函数返回值
成功:
0
出错:
返回错误码
表4列出了pthread_cancel()函数的语法要点。
表4pthread_cancel()函数语法要点
所需头文件
#include
函数原型
intpthread_cancel((pthread_tth)
函数传入值
th:
要取消的线程的标识符
函数返回值
成功:
0
出错:
返回错误码
(3)函数使用。
以下实例中创建了3个线程,为了更好地描述线程之间的并行执行,让3个线程重用同一个执行函数。
每个线程都有5次循环(可以看成5个小任务),每次循环之间会随机等待1~10s的时间,意义在于模拟每个任务的到达时间是随机的,并没有任何特定规律。
/*thread.c*/
#include
#include
#include
#defineTHREAD_NUMBER 3 /*线程数*/
#defineREPEAT_NUMBER 5 /*每个线程中的小任务数*/
#defineDELAY_TIME_LEVELS 10.0 /*小任务之间的最大时间间隔*/
void*thrd_func(void*arg)
{/*线程函数例程*/
intthrd_num=(int)arg;
intdelay_time=0;
intcount=0;
printf("Thread%disstarting\n",thrd_num);
for(count=0;count { delay_time=(int)(rand()*DELAY_TIME_LEVELS/(RAND_MAX))+1; sleep(delay_time); printf("\tThread%d: job%ddelay=%d\n", thrd_num,count,delay_time); } printf("Thread%dfinished\n",thrd_num); pthread_exit(NULL); } intmain(void) { pthread_tthread[THREAD_NUMBER]; intno=0,res; void*thrd_ret; srand(time(NULL)); for(no=0;no { /*创建多线程*/ res=pthread_create(&thread[no],NULL,thrd_func,(void*)no); if(res! =0) { printf("Createthread%dfailed\n",no); exit(res); } } printf("Createtreadssuccess\nWaitingforthreadstofinish...\n"); for(no=0;no { /*等待线程结束*/ res=pthread_join(thread[no],&thrd_ret); if(! res) { printf("Thread%djoined\n",no); } else { printf("Thread%djoinfailed\n",no); } } return0; } 以下是程序运行结果。 可以看出每个线程的运行和结束是独立与并行的。 $./thread Createtreadssuccess Waitingforthreadstofinish... Thread0isstarting Thread1isstarting Thread2isstarting Thread1: job0delay=6 Thread2: job0delay=6 Thread0: job0delay=9 Thread1: job1delay=6 Thread2: job1delay=8 Thread0: job1delay=8 Thread2: job2delay=3 Thread0: job2delay=3 Thread2: job3delay=3 Thread2: job4delay=1 Thread2finished Thread1: job2delay=10 Thread1: job3delay=4 Thread1: job4delay=1 Thread1finished Thread0: job3delay=9 Thread0: job4delay=2 Thread0finished Thread0joined Thread1joined Thread2joined 2.2 线程之间的同步与互斥 由于线程共享进程的资源和地址空间,因此在对这些资源进行操作时,必须考虑到线程间资源访问的同步与互斥问题。 这里主要介绍POSIX中两种线程同步机制,分别为互斥锁和信号量。 这两个同步机制可以互相通过调用对方来实现,但互斥锁更适合用于同时可用的资源是惟一的情况;信号量更适合用于同时可用的资源为多个的情况。 1.互斥锁线程控制 (1)函数说明。 互斥锁是用一种简单的加锁方法来控制对共享资源的原子操作。 这个互斥锁只有两种状态,也就是上锁和解锁,可以把互斥锁看作某种意义上的全局变量。 在同一时刻只能有一个线程掌握某个互斥锁,拥有上锁状态的线程能够对共享资源进行操作。 若其他线程希望上锁一个已经被上锁的互斥锁,则该线程就会挂起,直到上锁的线程释放掉互斥锁为止。 可以说,这把互斥锁保证让每个线程对共享资源按顺序进行原子操作。 互斥锁机制主要包括下面的基本函数。 n 互斥锁初始化: pthread_mutex_init() n 互斥锁上锁: pthread_mutex_lock() n 互斥锁判断上锁: pthread_mutex_trylock() n 互斥锁接锁: pthread_mutex_unlock() n 消除互斥锁: pthread_mutex_destroy() 其中,互斥锁可以分为快速互斥锁、递归互斥锁和检错互斥锁。 这3种锁的区别主要在于其他未占有互斥锁的线程在希望得到互斥锁时是否需要阻塞等待。 快速锁是指调用线程会阻塞直至拥有互斥锁的线程解锁为止。 递归互斥锁能够成功地返回,并且增加调用线程在互斥上加锁的次数,而检错互斥锁则为快速互斥锁的非阻塞版本,它会立即返回并返回一个错误信息。 默认属性为快速互斥锁。 (2)函数格式。 表5列出了pthread_mutex_init()函数的语法要点。 表5 pthread_mutex_init()函数语法要点 所需头文件 #include 函数原型 intpthread_mutex_init(pthread_mutex_t*mutex,constpthread_mutexattr_t*mutexattr) 函数传入值 mutex: 互斥锁 Mutexattr PTHREAD_MUTEX_INITIALIZER: 创建快速互斥锁 PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP: 创建递归互斥锁 PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP: 创建检错互斥锁 函数返回值 成功: 0 出错: 返回错误码 表6列出了pthread_mutex_lock()等函数的语法要点。 表6pthread_mutex_lock()等函数语法要点 所需头文件 #include 函数原型 intpthread_mutex_lock(pthread_mutex_t*mutex,) intpthread_mutex_trylock(pthread_mutex_t*mutex,) intpthread_mutex_unlock(pthread_mutex_t*mutex,) intpthread_mutex_destroy(pthread_mutex_t*mutex,) 函数传入值 mutex: 互斥锁 函数返回值 成功: 0 出错: -1 (3)使用实例。 下面的实例是在2.1小节示例代码的基础上增加互斥锁功能,实现原本独立与无序的多个线程能够按顺序执行。 /*thread_mutex.c*/ #include #include #include #defineTHREAD_NUMBER 3 /*线程数*/ #defineREPEAT_NUMBER 3 /*每个线程的小任务数*/ #defineDELAY_TIME_LEVELS10.0 /*小任务之间的最大时间间隔*/ pthread_mutex_tmutex; void*thrd_func(void*arg) { intthrd_num=(int)arg; intdelay_time=0,count=0; intres; /*互斥锁上锁*/ res=pthread_mutex_lock(&mutex); if(res) { printf("Thread%dlockfailed\n",thrd_num); pthread_exit(NULL); } printf("Thread%disstarting\n",thrd_num); for(count=0;count { delay_time=(int)(rand()*DELAY_TIME_LEVELS/(RAND_MAX))+1; sleep(delay_time); printf("\tThread%d: job%ddelay=%d\n",thrd_num,count,delay_time); } printf("Thread%dfinished\n",thrd_num); pthread_exit(NULL); } intmain(void) { pthread_tthread[THREAD_NUMBER]; intno=0,res; void*thrd_ret; srand(time(NULL)); /*互斥锁初始化*/ pthread_mutex_init(&mutex,NULL); for(no=0;no { res=pthread_create(&thread[no],NULL,thrd_func,(void*)no); if(res! =0) { printf("Createthread%dfailed\n",no); exit(res); } } printf("Createtreadssuccess\nWaitingforthreadstofinish...\n"); for(no=0;no { res=pthread_join(thread[no],&thrd_ret); if(! res) { printf("Thread%djoined\n",no); } else { printf("Thread%djoinfailed\n",no); } /*互斥锁解锁*/ pthread_mutex_unlock(&mutex); } pthread_mutex_destroy(&mutex); return0; } 该实例的运行结果如下所示。 这里3个线程之间的运行顺序跟创建线程的顺序相同。 $./thread_mutex Createtreadssuccess Waitingforthreadstofinish... Thread0isstarting Thread0: job0delay=7 Thread0: job1delay=7 Thread0: job2delay=6 Thread0finished Thread0joined Thread1isstarting Thread1: job0delay=3 Thread1: job1delay=5 Thread1: job2delay=10 Thread1finished Thread1joined Thread2isstarting Thread2: job0delay=6 Thread2: job1delay=10 Thread2: job2delay=8 Thread2finished Thread2joined
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- linux多线程设计 linux 多线程 设计