多线程编程

1.线程概念;
2.有关线程的一些理解;
3.线程的控制 ;
4.获取线程的id ;
5.线程的可结合和可分离;
6.线程切换的情况;
7.在Linux环境下进程与线程的区别.

1.线程概念
进程在各自独立的地址空间中运行,进程之间共享数据需要用mmap或者进程间通信机制,而今天介绍的多线程是进程内部执行的一个分支,同一进程的多个线程共享同一地址空间,因此文本段、数据段都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一 个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
(1)文件描述符表
(2)每种信号的处理方式(SIG_IGN、SIG_DFL或者自定义的信号处理函数)
(3)当前工作目录
(4)用户id和组id
(5)静态全局数据及堆上空间
但是线程也有它独立的部分,如下:
(1)线程id(tid)
(2)上下文,包括各种寄存器的值、程序计数器和栈指针
(3)栈空间
(4)errno变量
(5)信号屏蔽字,类似于umask,屏蔽权限
(6)调度优先级
下面介绍POSIX标准定义的线程库pthread,线程函数位于libpthread共享库中,由于使用了共享库因此在编译时要加上-lpthread选项。

2.有关线程的一些理解
(1)严格来说,Linux中时没有线程的概念,Linux中的线程都是通过用户用进程来模拟的,所以也将线程称为轻量级进程(LWP), ps -aL 命令就是来查看线程的详细信息;
(2)进程是操作系统分配资源的最小单位,而线程是调度的基本单位;
(3)线程是怎样模拟进程的
当内核创建一个进程时,随即就会创建进程控制块PCB等一系列数据结构来管理它,如下图:
这里写图片描述

在linux中可以通过vfork()函数来创建一个子进程,在vfork分流之后,如果子进程中没有使用exec函数进行程序替换的话,则子进程会在父进程的地址空间中运行父进程的代码。这时候PCB1(子进程进程控制块)和PCB(父进程的)就拥有相同的虚拟内存和相同的物理内存了,如果我们这时候再为PCB1创建一些它私有的资源(比如为它分配栈空间,给它独立出来一部分页表),那么PCB1就变成一个线程了。

这里简单说明一下fork,vfork的区别:
. vfork与fork都创建一个子进程,但vfork不将父进程的地址空间复制到子进程中,因为子进程会立即调用exec,于是不会访问该地址空间。在子进程调用exec或exit之前,它在父进程的空间中运行,也就是说会更改父进程的数据段、栈和堆。
. vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。

3.线程的控制
下面所介绍的函数都是POSIX标准制定的,所有函数都在线程共享库中,所以在使用的时候要引入头文件pthread.h
(1)线程的创建

int pthread_create(pthread_t tid,const pthread_attr_t *attr,void (start_routine)(void),void *arg);

1>返回值:成功返回0,失败返回错误码。可以通过char *strerror(int errnum)函数提取错误信息。
2>参数说明:
tid:用来保存线程的id,属于一个输出型参数.
attr:用来修改线程的属性。如果为NULL的时候表示创建的线程是默认属性。
start_routine:start_routine是一个函数指针,它指向一个返回值为void*,参数为void* 的函数,该线程将要执行的函数,当start_routine返回之后该线程就退出了。
arg:运行函数的参数。

(2)线程的终止
线程终止的三种方式:
1>return
使用return可以终止一个线程,但是如果是在main函数中调用return的话,相当于进程退出,那么所有线程都会退出,相当于调用exit或_exit函数。在任意一个线程中调用exit或_exit的话会使所有线程退出。

2>void pthread_exit(void *retval)
在线程中调用pthread_exit()可以使线程自己退出。退出的信息可以通过参数传出去,被等待他的线程获取。

3>int pthread_cancel (pthread_t tid)
在一个线程中调用pthread_cancel函数可以终止另一个线程。假设A线程是被pthread_cancel异常终止的,则pthread_join获取的线程A的退出码就是PTHREAD_CANCEL,这个宏的值是-1,可在pthread.h头文件中找到。
返回值:成功返回0,失败返回错误码。
tid:要终止的线程的id。

(3)线程等待
为什么要进行线程等待?
举个例子:在主线程之中创建一个新线程,假如在主线程要退出的情况下,新线程还没有执行完,那么当主线程退出后新线程也会立即退出,但是程序的运行结果可能就是错的。所以在这种情况下,需要主线程去等待新线程。

int pthread_join(pthread_t tid,void** retval);
1>功能:用来等待线程退出,并回收该线程的退出信息以及释放该进程的资源。
2>返回值:成功返回0,失败返回错误码。
3>参数说明:
tid:要等待线程的id。
retval:用来获取线程的退出信息。一般情况下,线程退出时不会立即释放它所占的所有资源,而是等待有人来获取它的退出信息之后才进行释放资源。否则会造成内存泄漏。

线程回收一般分两种情况:
1>默认情况下,线程被创建成可结合的。为了避免存储器泄漏,每个可结合线程都应该被显示地回收,即调用pthread_join ,如果该线程没有运行结束,调用者会被阻塞,在有些情况下我们并不希望如此,例如,在Web服务器中当主线程为每个新来的连接请求创建一个子线程进行处理的时候,主线程并不希望因为调用pthread_join而阻塞(因为还要继续处理之后到来的连接请求),所以就引来了将新线程从主线程中分离出去。

2>可采取在子线程中加入代码
pthread_detach(pthread_self())
或者父线程调用
pthread_detach(thread_id)(非阻塞,可立即返回)
这将该新线程的状态设置为分离的(detach),如此一来,该线程运行结束后会自动释放所有资源。
不过在主线程里分离新线程的话可能有一种情况就是新线程执行完了,才执行到主线程分离的地方,线程尽量在执行函数(新线程)里分离,要不然可能有线程安全的问题。

4.获取线程的id
pthread_t pthread_self();
在一个线程中调用这个函数会返回该线程的id。

5.线程的可结合和可分离
在任何一个时间点上,线程是可结合的或是可分离的。
可结合:
一个可结合的线程能够被其他线程回收资源和杀死。在被其他线程回收之前,它的存储资源是不释放的。
可分离:
一个可分离的线程是不能被其他线程回收或杀死的,它的存储器资源在他终止的时候由系统自动释放。一个可分离的线程是不能被等待的。
int pthread_detach(pthread_t tid);
这个函数可以将一个线程设置成可分离的。一个线程可以自己调用pthread_detach(pthread_self())将自己设置为可分离的。也可以通过别的线程将自己设置为可分离的。

默认情况下,线程被创建成可结合的。如果一个可结合的线程运行结束但没有被join,则它的状态类似于僵尸进程。也就是还有一部分资源没有被回收。 为了避免存储器泄漏,每一个可结合的线程都要么被显示地回收。即调用pthread_join等待进程运行结束,并可得到线程的退出码,回收其资源。或者是调用pthread_detach函数将线程分离。在调用pthread_join之后,如果该线程没有运行结束,则调用者会被阻塞。
当主线程退出后,就相当于进程退出了,这时候不管是可结合还是可分离的进程都会被回收结束掉。

6.线程切换的情况
(1)时间片到了;
(2)内核态切换到用户态。

7.在Linux环境下进程与线程的区别:
(1)进程是分配系统资源(CPU时间、内存)的基本单位
(2)线程是调度的基本单位,是进程中的一个执行流,进程被分配出来是被线程使用,同一进程内的多个线程共享进程的资源(线程之间交换信息的成本不大、强调共享的同时也需要私有化,即每个线程必须有自己的调度信息和优先级、硬件上下文、独立的运行时栈(保存变量等))
(3)进程不存在,线程也不存在;但是线程不存在,进程可以存在,即线程是进程的一部分 .
(4)进程强调独立,线程强调共享。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值