多线程编程

来源:互联网 发布:刚开的淘宝店怎么做 编辑:程序博客网 时间:2024/06/08 04:20

目录:

1、线程概念;

2、Linux下的线程的特点;

3、线程控制:线程创建/等待/终止


线程的概念:

说到线程的概念,我们就不由的想起来进程这一个概念了。要是不了解进程的话可以看看这个 什么是一个进程?


我们之前已经说过了什么是进程,我们都知道进程就是一个运行的程序,每一个进程在cpu都有一份独占的PCB结构,在这个PCB中,包含很多进程都有的东西,其中就包含一个我今天要说的 东西 ------地址空间。

说到地址空间,我们就可以引出我们今天的概念-----------------线程。。。。

线程,可以说是进程的一个简化,每一个线程就是进程的一个执行流 。他们之间可以共享当前进程的地址空间

所以线程之间的通信,相对于进程之间来说要相对容易一点。

线程与进程的区别:

1、进程强调的是资源独占;而线程强调的是资源共享;

2、进程与进程之间是没有任何交际的;同一个进程下的线程与线程之间共享地址空间;

3、线程是在进程的地址空间内部运行的;

4、进程是承担资源分配的基本单位;线程则是实现资源调度的基本单位;

5、线程上下文切换比进程上下文切换要快得多。


同一进程下的线程之间的都有哪些资源是共享的,哪些是私有的?


对同一个进程下面的多个进程

私有的资源主要有:

1、当前线程的id(可以说是线程的标识符);

2、上下文信息,包括各种寄存器的值,程序计数器、以及栈指针;(可以预防线程被突然切换出去,重新切换回来时可以继续按照上次的运行的位置正常运行)

3、私有的栈空间;(内部可以保存的一些线程独有的一些变量值)

4、线程的调度优先级;

共享的资源主要有:

1、文件描述符表;

2、每种信号的处理方式(SIG_IGN、SIG_DFL或者自定义的信号处理函数);

3、当前的工作目录;

4、用户id与组id;



Linux下的线程的特点


Linux下的线程设计的是非常的巧妙的,它是套用的是PCB,设计出来的一种轻量级的进程用来表示的线程。

说起Linux下的线程,的确不如windows下来的直接,windows中进程和线程有着明确的区分,各自有自己代表的数据结构、操作API,进程享有资源和线程参加调度。一切都是那么自然。Linux下进程和线程的区分就没有那么明显了。从最初的用户级线程模型,到后来内核级线程模型,Linux 对线程的支持虽然不如windows那么明显,但是却一直在努力。最初的用户级线程这里就不在多说,那么时代内核还没有加入对线程的支持,一切都是用户程序在YY,算不上是内核支持的线程。

LInuxthreads有以下特点:

1、管理线程的问题。每个进程拥有一个管理线程,进程内部线程的创建、销毁、同步都经过管理线程处理。通过管理线程,实现了下面对于多线程的要求:

  • 系统必须有能力杀死整个进程。
  • 线程栈的回收必须在对应线程结束后执行,所以线程不能自己处理。
  • 将要终止的线程必须能够被等待,从而不至于成为僵尸。
  • 线程数据区的回收需要对所有线程迭代执行,这个任务要管理线程完成。
  • 如果主线程需要调用pthread_exit(),而进程并没有终止,主线程就睡眠,直到其他线程终止后,由管理线程唤醒主线程。

2、为了维护线程数据和内存,LinuxThreads使用进程地址空间的高位部分,即低于进程栈空间的部分。

3、基于信号机制实现线程同步。

4、LinuxThreads把每个线程实现为一个进程,拥有一个唯一的进程ID。

5、当进程收到终止信号,由管理线程负责用此信号杀死其他线程。

6、如果某个异步信号被发送,管理线程把信号传递给某个线程,如果该线程当前阻塞了该信号,则信号就是pending状态。

7、内核调度器实现对线程的调度。


 基于上述特点,LinuxThreads的缺点就很明显了:

1、其使用一个管理线程创建和协调进程中线程,这就增加了创建/销毁线程的开销。

2、既然其核心思想依赖于管理线程,那么必然要进行许多围绕管理线程的上下文切换,从而影响扩展性和性能。

3、由于管理线程只有一个,只能运行在一个CPU,在多处理情况下,同步问题是一个大问题。

4、使用信号实现同步,基于信号性能问题,会增加操作响应延迟,由于不能专门对进程发送信号,也不遵循POSIX标准。

5、信号的处理时基于线程而不是基于进程,且信号的传递是串行化的,意味着如果某个线程阻塞了信号,信号不会被传递给其他线程直到该线程解除阻塞。

6、基于线程本质上是一个进程,那么那么同一进程下的线程user和groupID信息或许会不一致。对于setuid和setgid来讲会成为问题。

7、如果进程中某个线程发生crash,那么dump文件仅仅针对该线程而不是针对其所属进程(后期的LinuxThreads版本解决了这个问题)

8、由于每个线程也是一个进程,/proc文件系统会变得异常拥挤。且对于应用的线程数量也会受到限制。

9、线程数据的访问依赖于栈地址,不能直接获取,因此访问线程数据的速度较慢,且用户不能随便指定栈的大小,以防发生冲突。


线程控制


1、线程创建

在Linux系统下,如果要想在一个进程里创建一个线程的话,我们可以调用用户级的函数接口(因为Linux下没有明确的线程,所以有了这一用户级的库 pthread为我们提供现成线程函数):



2、线程等待

对于一个线程创建之后,这段代码运行完成之后,会有一个返回值,主线程需要回收这个运行结果,在这里我们使用线程等待来获取结果,用户库pthread为我们提供的库函数是:


3、线程终止

一个线程如果能够被创建的话,那当然也能被终止了;

要想实现一个线程的终止可以使用函数:


当然,我们也可以使用函数下面的函数来取消一个线程




代码实现线程等待与线程终止函数

#include<stdio.h>#include<pthread.h>//实现线程的那部分代码void * thread1(void * argc){printf("pid : %d  ,tid: %u\n",getpid(),pthread_self());//在此处调用pthread_exit函数pthread_exit(10);}void * thread2(void * argc){printf("pid : %d  ,tid: %u\n",getpid(),pthread_self());return (void*)20;}int main(){pthread_t  pthread1,pthread2;pthread_create(&pthread1,NULL,&thread1,NULL);pthread_create(&pthread2,NULL,&thread2,NULL);//调用线程取消函数pthread_cancel(pthread2);//在此处调用线程等待函数void * retval1=  NULL;void * retval2=  NULL;pthread_join(pthread1,&retval1);pthread_join(pthread2,&retval2);printf("thread1 ret is %d \n",retval1);printf("thread2 ret is %d \n",retval2);return  0;}


线程的分离与结合属性


在任何一个时间点上,线程是可结合的(joinable)或者是分离的(detached)。一个可结合的线程能够被其他线程收回其资源和杀死。在被其他线程回收之前,它的存储器资源(例如栈)是不释放的。相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器 资源在它终止时由系统自动释放。



默认情况下,线程被创建成可结合的。为了避免存储器泄漏,每个可结合线程都应该要么被显示地回收,即调用pthread_join;要么通过调用pthread_detach函数被分离。如果一个可结合线程结束运行但没有被join,则它的状态类似于进程中的Zombie Process,即还有一部分资源没有被回收,所以创建线程者应该调用pthread_join来等待线程运行结束,并可得到线程的退出代码,回收其资源。







原创粉丝点击