- 浏览: 142230 次
文章分类
最新评论
如同进程有一个进程 ID,每个线程也有一个线程 ID。不过进程 ID 在整个系统中是唯一的,而线程 ID 只有在它所属的进程上下文中才有意义。线程 ID 使用数据类型 pthread_t 来表示,因为这种类型在不同的实现中可能不同(有的为整型,有的为结构),所以为了移植性提供了一个比较函数 pthread_equal。另外,还提供了 pthread_self 函数来获取自身的线程 ID。
而要创建一个线程,可以使用 pthread_create 函数。
当该函数成功返回时,新创建的线程 ID 会被保存在 tidp 指向的内存中。attr 参数用于定制不同的线程属性,当为 NULL 时表示使用默认的属性。新创建的线程从 start_rtm 函数的地址开始运行,所需的参数放到无类型指针参数 arg 所指的地址中。如果有多个参数,可将其放到一个结构中,然后把该结构的地址作为 arg 参数传入(不过要保证该结构所使用的内存在调用者完成调用以后仍然有效,否则该结构的值可能会被其他使用了同一块栈内存的函数隐式地改变)。
线程创建时并不能保证是新创建的线程还是调用线程先运行。新创建的线程可以访问进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但是该线程的挂起信号集会被清除。
注意,pthread 类函数在调用失败时通常会返回错误码,而不是直接修改全局变量 errno,以便把错误的范围限制在引起出错的函数中。
如果进程中的任意线程调用了 exit、_exit 或者 _Exit,再或者收到默认动作是终止进程的信号都会终止整个进程。要使单个线程在不终止整个进程的情况下停止它的控制流,可以通过以下 3 种方式。
(1)从启动例程中返回,返回值是线程的退出码。
(2)被同一进程中的其他线程取消。
(3)调用 pthread_exit 函数。
pthread_exit 中的无类型指针参数 rval_ptr 用法同传给启动例程的单个参数类似。进程中的其他线程可以通过调用 pthread_join 函数访问到这个指针。pthread_join 会使调用线程一直阻塞,直到指定的线程调用 pthread_exit、从启动例程返回或者被取消。如果线程简单地从它的启动例程返回,rval_ptr 就包含返回码;如果线程被取消,由 rval_ptr 指定的内存单元就设置为 PTHREAD_CANCELED。如果对线程的返回值不感兴趣,可将 rval_ptr 设置为 NULL,这样调用 pthread_join 就只等待指定的线程终止而不获取线程的终止状态。
pthread_detach 函数可以分离线程。默认情况下,线程的终止状态会保存直到对该线程调用 pthread_join。如果线程已经被分离,线程的底层存储资源就可以在线程终止时立即被收回。线程分离后再调用 pthread_join 将会产生未定义行为。
虽然不能可移植的打印线程 ID,但可以写一个小的测试程序来完成这个任务。下面这个程序打印了进程 ID、新线程 ID 以及初始线程 ID。
在 Linux 上运行该程序,得到:
可见线程是运行在同一进程中,而这里的线程 ID 数据类型很像是一个指针(然而事实是 Linux 上的 pthread_t 类型是用无符号长整型来表示的)。
线程可以调用 pthread_cannel 函数来请求取消同一进程中的其他线程(如同进程中调用了 abort),也可以通过 pthread_cleanup_push 函数安排在线程退出时需要调用的线程清理处理程序(如同进程中的 atexit 函数),它们的执行顺序与注册顺序相反。
默认情况下,pthread_cancel 函数会使得线程 tid 表现得如同调用了参数为 PTHREAD_CANCELED 的 pthread_exit 函数,但线程可以选择忽略取消或者控制如何被取消。注意该函数并不等待线程终止,它仅仅提出请求。
当线程执行以下动作时,pthread_cleanup_push 函数就会调用清理函数 rtn,调用时只有一个参数 arg:
* 调用 pthread_exit 时;
* 相应取消请求时;
* 用非零 execute 参数调用 pthread_cleanup_pop 时。如果 execute 为 0,清理函数将不被调用。
不管发生上述哪种情况,pthread_cleanup_pop 都将删除上次 pthread_cleanup_push 调用建立的清理处理程序。注意,pthread_cleanup_pop 必须和 pthread_cleanup_push 在与线程相同的作用域中以匹配对的形式使用,因为它们可能实现为宏。
下面这个程序给出了一个如何使用线程清理处理程序的例子。这里为了匹配 pthread_cleanup_push,我们调用了参数为 0 的 pthread_cleanup_pop 函数。
Linux 上的运行结果:
从输出可以看出只有第二个线程的清理处理程序被调用了,因为第一个线程是使用 return 返回的,而第二个是调用 pthread_exit 返回的。另外,清理处理程序是按照与安装时的相反顺序被调用的。
如果在 FreeBSD 或者 Mac OS 上运行该程序将会出现段异常并产生 core 文件。因为这两个平台上的 pthread_cleanup_push 是用宏实现的,而宏把某些上下文存放在栈上。当线程 1 在调用 pthread_cleanup_push 和调用 pthread_cleanup_pop 之间返回时,栈已被改写,而这两个平台在调用清理程序时就用了这个被改写的上下文。唯一的可移植的方法是调用 pthread_exit。
#include <pthread.h> int pthread_equal(pthread_t tid1, pthread_t tid2); /* 返回值:若相等,返回非 0;否则,返回 0 */ pthread_t pthread_self(void); /* 返回值:调用线程的线程 ID */
而要创建一个线程,可以使用 pthread_create 函数。
#include <pthread.h> int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void* (*start_rtm)(void *), void *restrict arg); /* 返回值:若成功,返回 0;否则,返回错误编号 */
当该函数成功返回时,新创建的线程 ID 会被保存在 tidp 指向的内存中。attr 参数用于定制不同的线程属性,当为 NULL 时表示使用默认的属性。新创建的线程从 start_rtm 函数的地址开始运行,所需的参数放到无类型指针参数 arg 所指的地址中。如果有多个参数,可将其放到一个结构中,然后把该结构的地址作为 arg 参数传入(不过要保证该结构所使用的内存在调用者完成调用以后仍然有效,否则该结构的值可能会被其他使用了同一块栈内存的函数隐式地改变)。
线程创建时并不能保证是新创建的线程还是调用线程先运行。新创建的线程可以访问进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但是该线程的挂起信号集会被清除。
注意,pthread 类函数在调用失败时通常会返回错误码,而不是直接修改全局变量 errno,以便把错误的范围限制在引起出错的函数中。
如果进程中的任意线程调用了 exit、_exit 或者 _Exit,再或者收到默认动作是终止进程的信号都会终止整个进程。要使单个线程在不终止整个进程的情况下停止它的控制流,可以通过以下 3 种方式。
(1)从启动例程中返回,返回值是线程的退出码。
(2)被同一进程中的其他线程取消。
(3)调用 pthread_exit 函数。
#include <pthread.h> void pthread_exit(void *rval_ptr); int pthread_join(pthread_t tid, void **rval_ptr); int pthread_detach(pthread_t tid); /* 两个函数返回值:若成功,返回 0;否则,返回错误编码 */
pthread_exit 中的无类型指针参数 rval_ptr 用法同传给启动例程的单个参数类似。进程中的其他线程可以通过调用 pthread_join 函数访问到这个指针。pthread_join 会使调用线程一直阻塞,直到指定的线程调用 pthread_exit、从启动例程返回或者被取消。如果线程简单地从它的启动例程返回,rval_ptr 就包含返回码;如果线程被取消,由 rval_ptr 指定的内存单元就设置为 PTHREAD_CANCELED。如果对线程的返回值不感兴趣,可将 rval_ptr 设置为 NULL,这样调用 pthread_join 就只等待指定的线程终止而不获取线程的终止状态。
pthread_detach 函数可以分离线程。默认情况下,线程的终止状态会保存直到对该线程调用 pthread_join。如果线程已经被分离,线程的底层存储资源就可以在线程终止时立即被收回。线程分离后再调用 pthread_join 将会产生未定义行为。
虽然不能可移植的打印线程 ID,但可以写一个小的测试程序来完成这个任务。下面这个程序打印了进程 ID、新线程 ID 以及初始线程 ID。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> void printtid(const char *s){ pid_t pid = getpid(); pthread_t tid = pthread_self(); printf("%s pid %lu, tid %lu (0x%lx)\n", s, (unsigned long)pid, (unsigned long)tid, (unsigned long)tid ); } void *tfunc(void *arg){ printtid("new thread:"); pthread_exit((void *)1); // or return (void *)1; } int main(void){ pthread_t ntid; if(pthread_create(&ntid, NULL, tfunc, NULL) != 0){ printf("can't create thread\n"); exit(1); } printtid("main thread:"); void *tret; if(pthread_join(ntid, &tret) != 0){ printf("pthread_join error\n"); exit(1); } printf("thread exit code: %ld\n", (long)tret); exit(0); }
在 Linux 上运行该程序,得到:
$ ./print_tid.out main thread: pid 29340, tid 140178125190912 (0x7f7dc35b7700) new thread: pid 29340, tid 140178125182720 (0x7f7dc35b5700) thread exit code: 1
可见线程是运行在同一进程中,而这里的线程 ID 数据类型很像是一个指针(然而事实是 Linux 上的 pthread_t 类型是用无符号长整型来表示的)。
线程可以调用 pthread_cannel 函数来请求取消同一进程中的其他线程(如同进程中调用了 abort),也可以通过 pthread_cleanup_push 函数安排在线程退出时需要调用的线程清理处理程序(如同进程中的 atexit 函数),它们的执行顺序与注册顺序相反。
#include <pthread.h> int pthread_cancel(pthread_t tid); /* 返回值:若成功,返回 0;否则,返回错误编号*/ void pthread_cleanup_push(void (*rtn)(void *), void *arg); void pthread_cleanup_pop(int execute);
默认情况下,pthread_cancel 函数会使得线程 tid 表现得如同调用了参数为 PTHREAD_CANCELED 的 pthread_exit 函数,但线程可以选择忽略取消或者控制如何被取消。注意该函数并不等待线程终止,它仅仅提出请求。
当线程执行以下动作时,pthread_cleanup_push 函数就会调用清理函数 rtn,调用时只有一个参数 arg:
* 调用 pthread_exit 时;
* 相应取消请求时;
* 用非零 execute 参数调用 pthread_cleanup_pop 时。如果 execute 为 0,清理函数将不被调用。
不管发生上述哪种情况,pthread_cleanup_pop 都将删除上次 pthread_cleanup_push 调用建立的清理处理程序。注意,pthread_cleanup_pop 必须和 pthread_cleanup_push 在与线程相同的作用域中以匹配对的形式使用,因为它们可能实现为宏。
下面这个程序给出了一个如何使用线程清理处理程序的例子。这里为了匹配 pthread_cleanup_push,我们调用了参数为 0 的 pthread_cleanup_pop 函数。
#include <stdio.h> #include <stdlib.h> #include <pthread.h> void cleanup(void *arg){ printf("cleanup: %s\n", (char *)arg); } void *thr_fn1(void *arg){ printf("thread 1 starts push\n"); pthread_cleanup_push(cleanup, "first clean handler in thread 1"); pthread_cleanup_push(cleanup, "second clean handler in thread 1"); printf("thread 1 push complete\n"); if(arg) return (void *)1; pthread_cleanup_pop(0); pthread_cleanup_pop(0); return (void *)1; } void *thr_fn2(void *arg){ printf("thread 2 starts push\n"); pthread_cleanup_push(cleanup, "first clean handler in thread 2"); pthread_cleanup_push(cleanup, "second clean handler in thread 2"); printf("thread 2 push complete\n"); if(arg) pthread_exit((void *)2); pthread_cleanup_pop(0); pthread_cleanup_pop(0); pthread_exit((void *)2); } int main(void){ pthread_t tid1, tid2; void *rtn; if(pthread_create(&tid1, NULL, thr_fn1, (void *)1) != 0){ printf("pthread_create 1 error\n"); exit(1); } if(pthread_create(&tid2, NULL, thr_fn2, (void *)1) != 0){ printf("pthread_create 2 error\n"); exit(1); } if(pthread_join(tid1, &rtn) != 0){ printf("pthread_join 1 error\n"); exit(1); } printf("thread 1 return code: %ld\n", (long)rtn); if(pthread_join(tid2, &rtn) != 0){ printf("pthread_join 2 error\n"); exit(2); } printf("thread 2 exit code: %ld\n", (long)rtn); exit(0); }
Linux 上的运行结果:
$ ./threadClean.out thread 2 starts push thread 2 push complete cleanup: second clean handler in thread 2 cleanup: first clean handler in thread 2 thread 1 starts push thread 1 push complete thread 1 return code: 1 thread 2 exit code: 2
从输出可以看出只有第二个线程的清理处理程序被调用了,因为第一个线程是使用 return 返回的,而第二个是调用 pthread_exit 返回的。另外,清理处理程序是按照与安装时的相反顺序被调用的。
如果在 FreeBSD 或者 Mac OS 上运行该程序将会出现段异常并产生 core 文件。因为这两个平台上的 pthread_cleanup_push 是用宏实现的,而宏把某些上下文存放在栈上。当线程 1 在调用 pthread_cleanup_push 和调用 pthread_cleanup_pop 之间返回时,栈已被改写,而这两个平台在调用清理程序时就用了这个被改写的上下文。唯一的可移植的方法是调用 pthread_exit。
发表评论
-
打开伪终端设备
2018-07-09 20:50 1250在伪终端概述一节中已对 PTY进行了初步的介绍。尽管 ... -
伪终端概述
2018-06-02 11:05 1548伪终端就是指,一个应用程序看上去像一个终端,但事实上它 ... -
终端窗口大小和 termcap
2018-05-29 22:39 798多数 UNIX 系统都提供了一种跟踪当前终端窗口大小的 ... -
终端规范模式和非规范模式
2018-05-29 00:25 948终端规范模式很简单:发一个读请求,当一行已经输入后,终 ... -
终端标识
2018-05-23 11:18 568尽管控制终端的名字在多数 UNIX 系统上都是 /de ... -
波特率和行控制函数
2018-05-22 07:53 943虽然大多数终端设 ... -
终端属性和选项标志
2018-05-20 07:40 709tcgetattr 和 tcsetattr ... -
终端特殊输入字符
2018-05-17 06:33 814终端支持下表所示的特殊输入字符。 为了更改 ... -
终端 I/O 综述
2018-05-10 07:56 437终端设备可认为是由内核中的终端驱动程序控制的。每个终端 ... -
POSIX 信号量
2018-05-09 00:03 579在XSI IPC通信之信 ... -
XSI IPC 通信之共享存储
2018-04-25 07:18 946在XSI IPC通信之消息队列和XSI IPC通信之信 ... -
XSI IPC通信之信号量
2018-04-17 23:38 616在XSI IPC通信之消 ... -
XSI IPC通信之消息队列
2018-04-15 10:54 497消息队列是消息的链接表,存储在内核中,由消息队列标识符 ... -
XSI IPC 相似特征介绍
2018-02-08 23:48 484有 3 种称作 XSI IPC ... -
IPC 通信之 FIFO
2018-02-06 22:55 419FIFO 也被称为命名管道,未命名的管道只能在两个相关 ... -
IPC 通信之管道
2018-01-30 22:22 389管道是 UNIX 系统 IPC 的最古老但也是最常用的 ... -
readv/writev 函数及存储映射 I/O
2018-01-19 00:57 887readv 和 writev 函数可用于在一次函数调用 ... -
POSIX 异步 I/O
2018-01-16 21:33 454POSIX 异步 I/O 接口为对不同类型的文件进行异 ... -
fcntl 记录锁
2018-01-06 23:48 614记录锁的功能是:当有进程正在读或修改文件的某个部分时, ... -
守护进程惯例
2018-01-06 23:52 438UNIX 系统中,守护进程遵循下列通用惯例。 ...
相关推荐
`Thread`类是创建线程的基础,它可以接受一个`target`参数,这个参数是线程要执行的函数。`Lock`和`Rlock`类用于线程间的同步,防止数据竞争问题。`Condition`类提供了基于条件变量的同步,而`Semaphore`和`...
Java程序中的线程是在操作系统级别的线程基础上进行抽象的。每个Java程序都有一个主线程,即由JVM启动并执行main方法的线程。线程代表了程序中的执行流,可以在不同的线程之间切换以共享CPU时间。线程的状态包括新建...
在标题提到的"四个显示线程计算结果的函数"中,这通常指的是在多线程环境下,如何在主线程(通常是UI线程)中安全地更新界面以展示从属线程计算的结果。 在描述中,提到的问题是关于从属线程如何在主界面上显示中间...
MFC支持多线程编程,并提供了CWinThread类作为线程的基础,通过继承这个类并重写它的成员函数,我们可以创建自己的线程类。 总的来说,这个压缩包可能包含了一些关于如何使用MFC进行多线程编程,特别是利用函数指针...
综上所述,线程管理和时间控制是复杂系统设计的关键技术,而模块化和工程方法则是实现高效、可维护代码的基础。掌握这些概念和技术对于任何IT专业人员来说都是至关重要的。在实际项目中,结合有限状态自动机和概要...
线程创建是 LINUX 线程编程的基础,pthread_create 函数是创建线程的主要函数。该函数的参数包括线程标识符、线程属性、线程入口函数和线程参数。pthread_create 函数的返回值为 0 表示创建成功,否则表示创建失败。...
### C#多线程基础详解 #### 知识点一:多线程概念与优势 在C#中,多线程是指一个程序中同时执行多个线程的能力。一个线程是程序执行的基本单位,每个线程都有自己的指令指针、栈和局部变量,但共享相同的内存空间...
`CWinThread`提供了线程的基础设施,包括消息泵和线程同步机制。创建一个新线程的第一步是派生一个新的类自`CWinThread`,并重写其`Run`成员函数,这个函数将作为线程的入口点。 当需要在线程中使用对话框类中的...
篇文章深入探讨了C++中当析构函数遇到多线程时所面临的挑战,以及如何通过Boost库中的`shared_ptr`和`weak_ptr`智能指针来解决这一问题。文章作者陈硕,在2009年上海C++技术大会上分享了这一主题,并在此基础上进行...
**多线程基础** 多线程是指在一个进程中可以同时运行多个独立的执行流,每个执行流被称为一个线程。在MFC中,我们可以使用`CWinThread`类来创建和管理线程。`CWinThread`是MFC对Windows API中的`_beginthreadex`和`...
在Linux系统中,线程是进程内的执行单元,它们共享同一地址空间,但拥有独立的栈、...正确理解和使用这些函数是编写多线程程序的基础,能够帮助开发者有效地利用多核处理器的并行计算能力,提高程序的性能和响应性。
### VC 线程基础知识总结 #### 一、线程概念与重要性 在计算机科学领域,线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。程序员可以通过多线程技术,实现并发执行任务,...
理解和熟练掌握这些函数的使用是编写多线程应用程序的基础,对于软件开发者来说至关重要。在实际编程中,还需要注意线程安全问题,例如避免数据竞争和死锁,合理使用同步机制,如互斥锁(mutex)、条件变量(cond)...
1. **线程基础**: - 在多线程编程中,线程是程序执行的最小单元,每个线程都有自己的执行路径和堆栈。 - Windows API提供了创建和管理线程的函数,如`CreateThread()`和`WaitForSingleObject()`。 2. **创建线程...
### 一、Web Workers:JavaScript的多线程基础 Web Workers是JavaScript实现多线程的核心技术,它允许在后台线程中执行脚本,以避免阻塞主线程(用户界面)。创建一个新的Worker对象可以启动一个后台线程,通过post...
在IT行业中,多线程是程序并发执行的一种方式,它极大地提高了系统资源的利用率和程序...在学习过程中,可以参考"多线程基础总结01.bmp"和"多线程基础总结01.mmap"等文件,它们可能提供了更为详细和直观的结构化知识。
在VC++编程中,创建和显示窗口是应用程序的基础操作,特别是在多线程环境中,这将涉及到线程同步、窗口消息处理等复杂概念。本篇将深入讲解如何在VC++的线程中创建并显示窗口,以及相关的技术要点。 首先,我们要...
以上只是进程和线程管理API函数的一部分,它们构成了现代操作系统和多线程应用程序的基础。熟练掌握这些函数的使用,对于开发高性能、高并发的软件系统至关重要。通过合理利用这些API,开发者可以构建出既高效又稳定...
参数包括线程标识符、线程属性、线程入口函数以及传递给该函数的参数。例如: ```c pthread_t thread_id; int ret = pthread_create(&thread_id, NULL, entry_function, arg); ``` 其中,`entry_function`是...
本篇文章将详细介绍Linux环境下多线程编程的基础函数及其实现机制,特别是线程的创建与取消功能。 #### 二、线程与进程 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。...