深入浅出 多线程(1)

追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

导读:本篇文章讲解 深入浅出 多线程(1),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

Linux线程概述

线程是程序中完成一个独立任务的完整执行序列,即一个可调度的实体。根据运行环境和调度者的身份,线程可分为内核线程和用户线程
内核线程,在有的系统上也称为LWP(Light Weight Process,轻量级进程),运行在内核空间,由内核来调度﹔
用户线程运行在用户空间,由线程库来调度。当进程的一个内核线程获得CPU的使用权时,它就加载并运行一个用户线程。可见,内核线程相当于用户线程运行的“容器”。
一个进程可以拥有M个内核线程和N个用户线程,其中M≤N。并且在一个系统的所有进程中,M和N的比值都是固定的。按照M;N的取值,线程的实现方式可分为三种模式:完全在用户空间实现、完全由内核调度和双层调度(two level scheduler)。

完全在用户空间实现的线程无须内核的支持,内核甚至根本不知道这些线程的存在。线程库负责管理所有执行线程,比如线程的优先级、时间片等。线程库利用longjmp来切换线程的执行,使它们看起来像是“并发”执行的。但实际上内核仍然是把整个进程作为最小单位来调度的。换句话说,一个进程的所有执行线程共享该进程的时间片,它们对外表现出相同的优先级。因此,对这种实现方式而言,N=1,即M个用户空间线程对应1个内核线程,而该内核线程实际上就是进程本身

全在用户空间实现的线程的优点是﹔创建和调度线程都无须内核的干预,因此速度相当快。并且由于它不占用额外的内核资源,所以即使一个进程创建了很多线程,也不会对系统性能造成明显的影响。其缺点是﹔对于多处理器系统,一个进程的多个线程无法运行在不同的CPU上,因为内核是按照其最小调度单位来分配CPU的。此外,线程的优先级只对同一个进程中的线程有效,比较不同进程中的线程的优先级没有意义。早期的伯克利UNIX线程就是采用这种方式实现的。

完全由内核调度的模式将创建、调度线程的任务都交给了内核,运行在用户空间的线程库无须执行管理任务,这与完全在用户空间实现的线程恰恰相反。二者的优缺点也正好互换。较早的Linux内核对内核线程的控制能力有限,线程库通常还要提供额外的控制能力,尤其是线程同步机制,不过现代Linux内核已经大大增强了对线程的支持。完全由内核调度的这种线程实现方式满足M:N=1:1,即1个用户空间线程被映射为1个内核线程。

双层调度模式是前两种实现模式的混合体:内核调度M个内核线程,线程库调度N个用户线程。这种线程实现方式结合了前两种方式的优点:不但不会消耗过多的内核资源,而且线程切换速度也较快,同时它可以充分利用多处理器的优势。

Linux线程库

Linux上两个最有名的线程库是LinuxThreads和 NPTL,它们都是采用1:1的方式实现的。由于LinuxThreads在开发的时候,Linux内核对线程的支持还非常有限,所以其可用性、稳定性以及POSIX兼容性都远远不及NPTL。现代Linux上默认使用的线程库是NPTL。用户可以使用如下命令来查看当前系统上所使用的线程库:

[root@192 IO_REUSE]# getconf GNU_LIBPTHREAD_VERSION
NPTL 2.17

LinuxThreads线程库一个有名的特性是所谓的管理线程。它是进程中专门用于管理其它工作线程:
1.系统发送给进程的终止信号先由管理线程接收,管理线程再给其他工作线程发送同样的信号以终止它们。
2.系统发送给进程的终止信号先由管理线程接收,管理线程再给其他工作线程发送同样的信号以终止它们。
3.如果主线程先于其他工作线程退出,则管理线程将阻塞它,直到所有其他工作线程都结束之后才唤醒它。
4.回收每个线程堆栈使用的内存。

管理线程的引人,增加了额外的系统开销。并且由于它只能运行在一个CPU上,所以LinuxThreads 线程库也不能充分利用多处理器系统的优势。

所以出现NPTL:
1.内核线程不再是一个进程,因此避免了很多用进程模拟内核线程导致的语义问题。
2.摒弃了管理线程,终止线程、回收线程堆栈等工作都可以由内核来完成。
3.由于不存在管理线程,所以一个进程的线程可以运行在不同的CPU上,从而充分利用了多处理器系统的优势。
4.线程的同步由内核来完成。隶属于不同进程的线程之间也能共享互斥锁,因此可实现跨进程的线程同步。

创建线程和结束进程

include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);
typedef unigned long int pthread_t

void pthread_exit(void* retval)

void pthread_join(pthread_t thread,void retval);

 int pthread_cancel(pthread_t thread);
 int pthread_setcancelstate(int state, int *oldstate);
 int pthread_setcanceltype(int type, int *oldtype);

pthread_exit:
线程一旦被创建好,内核就可以调度内核线程来执行start_routine函数指针所指向的函数了。线程函数在结束时最好调用如下函数,以确保安全、干净地退出:
void pthread_exit(void* retval)
pthread_exit 函数通过retval参数向线程的回收者传递其退出信息。它执行完之后不会返回到调用者,而且永远不会失败。

pthread_join:
一个进程中的所有线程都可以调用pthread_join函数来回收其他线程(前提是目标线程是可回收的,见后文),即等待其他线程结束,这类似于回收进程的wait和 waitpid系统调用
phread参数是目标线程的标识符,retval参数则是目标线程返回的退出信息。该函数会一直阻塞,直到被回收的线程结束为止。该函数成功时返回0,失败则返回错误码。下图:

错误码 描述
EDEADLK 可能引起死锁。比如两个线程互相针对对方调用pthread_join,或者线程对自身调用pthread_join目标线程是不可回收的,或者已经有其他线程在回收该目标线程
EINVAL 目标线程是不可回收的,或者已经有其他线程在同收该目标线程
ESRCH 目标线程不存在

pthread_cancel:
有时候我们希望异常终止一个线程,即取消线程

pthread_setcancelstate(int state,int*oldstate);
这两个函数的第一个参数分别用于设置线程的取消状态(是否允许取消)和取消类型(如何取消),第二个参数则分别记录线程原来的取消状态和取消类型。state参数有两个可选项:

PTHREAD_CANCEL_ENABLE,允许线程被取消。它是线程被创建时的默认取消状态。
PTHREAD_CANCEL_DISABLE,禁止线程被取消。这种情况下,如果一个线程收到取消请求,则它会将请求挂起,直到该线程允许被取消。

type可选值:

PTHREAD_CANCEL_ASYNCHRONOUS,线程随时都可以被取消。它将使得接收到取消请求的目标线程立即采取行动。
PTHREAD_CANCEL_DEFERRED,允许目标线程推迟行动,直到它调用了下面几个所谓的取消点函数中的一个: pthread_join、pthread_testcancel、pthread_cond_wait、pthread_cond_timedwait、sem_wait和 sigwait。根据POSIX标准,其他可能阻塞的系统调用,比如 read、wait,也可以成为取消点。不过为了安全起见,我们最好在可能会被取消的代码中调用pthread_testcancel函数以设置取消点。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/129698.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
半码博客——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!