参考引用
并行指的是可以并排/并列执行多个任务,这样的系统通常有多个执行单元可以实现并行运行,如:并行运行 task1、task2、task3
并行运行并不一定要同时开始运行、同时结束运行,只需满足在某一个时间段上存在多个任务被多个执行单元同时在运行着
相比于串行和并行,并发强调的是一种分时复用
总结
多核处理器和单核处理器
- 对于单核处理器来说,只有一个执行单元,同时只能执行一条指令
- 对于多核处理起来说,有多个执行单元,在操作系统中,多个执行单元以并行方式运行多个进程,同时每一个执行单元以并发方式运行系统中的多个线程(进程级并行和线程级并发)
- 在单个处理核心虽然以并发方式运行着系统中的线程(微观上交替/交叉方式运行不同的线程),但在宏观上所表现出来的效果是同时运行着系统中的所有线程,因为处理器的运算速度太快了,交替轮训一次所花费的时间在宏观上几乎是可以忽略不计的,所以表示出来的效果就是同时运行着所有线程
每个线程有其对应的标识,称为线程 ID
进程 ID 使用 pid_t 数据类型来表示(非负整数),而线程 ID 使用 pthread_t 数据类型(unsigned long int)表示
#include <pthread.h>
pthread_t pthread_self(void);
#include <pthread.h>
// 如果两个线程 ID t1 和 t2 相等,则 pthread_equal() 返回一个非零值;否则返回 0
int pthread_equal(pthread_t t1, pthread_t t2);
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
主线程使用库函数 pthread_create() 创建一个新的线程,称为主线程的子线程
线程创建成功,新线程就会加入到系统调度队列中,获取到 CPU 之后就会立马从 start_routine() 函数开始运行该线程的任务
示例: pthread_create() 创建线程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
static void *new_thread_start(void *arg) {
printf("新线程: 进程 ID<%d> 线程 ID<%lu>\n", getpid(), pthread_self());
return (void *)0;
}
int main(void) {
pthread_t tid;
int ret;
ret = pthread_create(&tid, NULL, new_thread_start, NULL);
if (ret) {
fprintf(stderr, "Error: %s\n", strerror(ret));
exit(-1);
}
printf("主线程: 进程 ID<%d> 线程 ID<%lu>\n", getpid(), pthread_self());
// 主线程休眠 1 秒钟,如果主线程不进行休眠,它就会立马退出
// 这样可能会导致新创建的线程还没有机会运行,整个进程就结束了
sleep(1);
exit(0);
}
# 使用 -l 选项指定链接库 pthread,原因在于 pthread 不在 gcc 的默认链接库中,所以需要手动指定
$ gcc thread.c -o thread -l pthread
yxd@yxd-VirtualBox:~/Desktop/test$ ./thread
# 新创建的线程与主线程属于同一个进程,但是它们的线程 ID 不同
主线程: 进程 ID<2624> 线程 ID<140143689406272>
新线程: 进程 ID<2624> 线程 ID<140143681070848>
终止线程的方式
如果进程中的任意线程调用 exit()、_exit() 或 _Exit(),那么将会导致整个进程终止
pthread_exit() 函数将终止调用它的线程
#include <pthread.h>
// 1. 参数 retval 的数据类型为 void*,指定了线程的返回值,也就是线程的退出码
// 该返回值可由另一个线程通过调用 pthread_join() 来获取
// 2. 参数 retval 所指向的内容不应分配于线程栈中,因为线程终止后,将无法确定线程栈的内容是否有效
void pthread_exit(void *retval);
调用 pthread_exit() 相当于在线程的 start 函数中执行 return 语句
示例:pthread_exit() 终止线程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
static void *new_thread_start(void *arg) {
printf("新线程 start\n");
sleep(1); // 新线程中调用 sleep() 休眠,保证主线程先调用 pthread_exit() 终止
printf("新线程 end\n");
pthread_exit(NULL);
}
int main(void) {
pthread_t tid;
int ret;
ret = pthread_create(&tid, NULL, new_thread_start, NULL);
if (ret) {
fprintf(stderr, "Error: %s\n", strerror(ret));
exit(-1);
}
printf("主线程 end\n");
pthread_exit(NULL);
exit(0);
}
$ gcc thread1.c -o thread1 -l pthread
$ ./thread1
主线程 end
新线程 start
新线程 end
在父、子进程当中,父进程可通过 wait() 函数(或 waitpid())阻塞等待子进程退出并获取其终止状态,回收子进程资源;而在线程当中通过调用 pthread_join() 函数来阻塞等待线程终止,并获取线程的退出码以回收线程资源
#include <pthread.h>
// thread:pthread_join() 等待指定线程的终止,通过参数 thread(线程 ID)指定需要等待的线程
// retval:如果参数 retval 不为 NULL,则 pthread_join()将目标线程的退出状态复制到 *retval 所指向的内存区域
// 如果目标线程被 pthread_cancel() 取消,则将 PTHREAD_CANCELED 放在 *retval 中
// 如果对目标线程的终止状态不感兴趣,则可将参数 retval 设置为 NULL
// 返回值:成功返回 0;失败将返回错误码
int pthread_join(pthread_t thread, void **retval);
调用 pthread_join() 函数将会以阻塞的形式等待指定的线程终止
- 僵尸线程除了浪费系统资源外,若僵尸线程积累过多会导致应用程序无法创建新的线程
- 如果进程中存在着僵尸线程并未得到回收,当进程终止之后,进程会被其父进程回收,所以僵尸线程同样也会被回收
pthread_join() 执行的功能类似于针对进程的 waitpid() 调用,不过存在一些显著差别
示例:pthread_join() 等待线程终止
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
// 主线程调用 pthread_create() 创建新线程之后,新线程执行 new_thread_start()函数
static void *new_thread_start(void *arg) {
printf("新线程 start\n");
sleep(2);
printf("新线程 end\n");
pthread_exit((void *)10);
}
int main(void) {
pthread_t tid;
void *tret;
int ret;
ret = pthread_create(&tid, NULL, new_thread_start, NULL);
if (ret) {
fprintf(stderr, "pthread_cread error: %s\n", strerror(ret));
exit(-1);
}
// 主线程中调用 pthread_join() 阻塞等待新线程终止,新线程终止后,pthread_join() 返回
// 将目标线程的退出码保存在 *tret 所指向的内存中
ret = pthread_join(tid, &tret);
if (ret) {
fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
exit(-1);
}
printf("新线程终止, code=%ld\n", (long)tret);
exit(0);
}
$ gcc thread2.c -o thread2 -l pthread
$ ./thread2
新线程 start
新线程 end
新线程终止, code=10
通过调用 pthread_cancel() 库函数向一个指定的线程发送取消请求
#include <pthread.h>
int pthread_cancel(pthread_t thread);
示例:pthread_cancel() 取消线程使用
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
static void *new_thread_start(void *arg) {
printf("新线程 start\n");
for (;;) {
sleep(1);
}
return (void *)0;
}
int main(void) {
pthread_t tid;
void *tret;
int ret;
/* 创建新线程 */
ret = pthread_create(&tid, NULL, new_thread_start, NULL);
if (ret) {
fprintf(stderr, "pthread_cread error: %s\n", strerror(ret));
exit(-1);
}
sleep(1);
/* 向新线程发送取消请求 */
ret = pthread_cancel(tid);
if (ret) {
fprintf(stderr, "pthread_cancel error: %s\n", strerror(ret));
exit(-1);
}
/* 等待新线程终止 */
ret = pthread_join(tid, &tret);
if (ret) {
fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
exit(-1);
}
printf("新线程终止, code=%ld\n", (long)tret);
exit(0);
}
$ gcc thread3.c -o thread3 -l pthread
$ ./thread3
新线程 start
新线程终止, code=-1
#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
int pthread_setcanceltype(int type, int *oldtype);
pthread_setcancelstate() 函数会将调用线程的取消性状态设置为参数 state 中给定的值,并将线程之前的取消性状态保存在参数 oldstate 指向的缓冲区中,如果对之前的状态不感兴趣,则将参数 oldstate 设置为 NULL
pthread_setcancelstate() 调用成功将返回 0,失败返回非 0 值的错误码
pthread_setcancelstate() 函数执行的设置取消状态和获取旧状态操作,这两步是一个原子操作
参数 state 必须是以下值之一
示例:修改 6.1 小节下述代码
...
static void *new_thread_start(void *arg) {
/* 设置为不可被取消 */
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
for (;;) {
printf("新线程--running\n");
sleep(2);
}
return (void *)0;
}
...
$ gcc thread4.c -o thread4 -l pthread
$ ./thread4
新线程--running
新线程--running
新线程--running
...
若将线程的取消类型设置为 PTHREAD_CANCEL_DEFERRED 时(线程可以取消状态下),收到其它线程发送过来的取消请求时,仅当线程抵达某个取消点时,取消请求才会起作用
什么是取消点?
取消点函数
# Cancellation points
$ man 7 pthreads
...
static void *new_thread_start(void *arg) {
printf("新线程--running\n");
for (;;) {
}
return (void *)0;
}
...
如何解决上述由于不存在取消点导致无法取消请求的问题?
#include <pthread.h>
void pthread_testcancel(void);
示例:使用 pthread_testcancel() 产生取消点
// 修改 6.1 小节下述代码
...
static void *new_thread_start(void *arg) {
printf("新线程--start run\n");
for (;;) {
pthread_testcancel();
}
return (void *)0;
}
...
$ gcc thread5.c -o thread5 -l pthread
$ ./thread5
新线程--start run
新线程终止,code=-1
默认情况下,当线程终止时,其它线程可以通过调用 pthread_join() 获取其返回状态并回收线程资源,有时并不关心线程的返回状态,只希望系统在线程终止时能够自动回收线程资源并将其移除
#include <pthread.h>
int pthread_detach(pthread_t thread);
示例:pthread_detach() 分离线程使用
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
static void *new_thread_start(void *arg) {
int ret;
/* 自行分离 */
ret = pthread_detach(pthread_self());
if (ret) {
fprintf(stderr, "pthread_detach error: %s\n", strerror(ret));
return NULL;
}
printf("新线程 start\n");
sleep(2); // 休眠 2 秒钟
printf("新线程 end\n");
pthread_exit(NULL);
}
int main(void) {
pthread_t tid;
int ret;
/* 创建新线程 */
ret = pthread_create(&tid, NULL, new_thread_start, NULL);
if (ret) {
fprintf(stderr, "pthread_cread error: %s\n", strerror(ret));
exit(-1);
}
// 休眠 1 秒钟,确保调用 pthread_join() 函数时新线程已经将自己分离,此时主线程调用 pthread_join() 必然会失败
sleep(1);
/* 等待新线程终止 */
ret = pthread_join(tid, NULL);
if (ret) {
fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
}
pthread_exit(NULL);
}
$ gcc thread4.c -o thread4 -l pthread
$ ./thread4
新线程 start
pthread_join error: Invalid argument
新线程 end
调用 pthread_create() 创建线程,可对新建线程的各种属性进行设置,Linux 使用 pthread_attr_t 数据类型定义线程的所有属性
当定义 pthread_attr_t 对象后,需要使用 pthread_attr_init() 函数对该对象进行初始化操作,当对象不再使用时,需要使用 pthread_attr_destroy() 函数将其销毁
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
每个线程都有自己的栈空间,pthread_attr_t 数据结构中定义了栈的起始地址以及栈大小
#include <pthread.h>
// attr:指向线程属性对象
// stackaddr:栈起始地址信息保存在 *stackaddr 中
// stacksize:栈大小信息保存在参数 stacksize 所指向的内存中
int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);
// attr:指向线程属性对象
// stackaddr:设置栈起始地址为指定值
// stacksize:设置栈大小为指定值
int pthread_attr_getstack(const pthread_attr_t *attr, void **stackaddr, size_t *stacksize);
示例:创建新的线程并将线程栈大小设置为 4Kb
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
static void *new_thread_start(void *arg) {
puts("Hello World!");
return (void*)0;
}
int main(int argc, char *argv[]) {
pthread_attr_t attr;
size_t stacksize;
pthread_t tid;
int ret;
/* 对 attr 对象进行初始化 */
pthread_attr_init(&attr);
/* 设置栈大小为 4K */
pthread_attr_setstacksize(&attr, 4096);
/* 创建新线程 */
ret = pthread_create(&tid, &attr, new_thread_start, NULL);
if (ret) {
fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
exit(-1);
}
/* 等待新线程终止 */
ret = pthread_join(tid, NULL);
if (ret) {
fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
exit(-1);
}
/* 销毁 attr 对象 */
pthread_attr_destroy(&attr);
exit(0);
}
$ gcc stack.c -o stack -l pthread
$ ./stack
Hello World!
如果在创建线程时就确定要将该线程分离,可以修改 pthread_attr_t 结构中的 detachstate 线程属性,让线程一开始运行就处于分离状态
#include <pthread.h>
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
参数 detachstate 取值如下
示例:以分离状态启动线程
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
static void *new_thread_start(void *arg) {
puts("Hello World!");
return (void *)0;
}
int main(int argc, char *argv[]) {
pthread_attr_t attr;
pthread_t tid;
int ret;
/* 对 attr 对象进行初始化 */
pthread_attr_init(&attr);
/* 设置以分离状态启动线程 */
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
/* 创建新线程 */
ret = pthread_create(&tid, &attr, new_thread_start, NULL);
if (ret) {
fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
exit(-1);
}
sleep(1);
/* 销毁 attr 对象 */
pthread_attr_destroy(&attr);
exit(0);
}
$ gcc detach.c -o detach -l pthread
$ ./detach
Hello World!
除了以上提到的三种情况外,其它机制产生的信号均属于进程层面,如:其它进程调用 kill() 或 sigqueue() 所发送的信号;用户在终端按下 Ctrl+C、Ctrl+\、Ctrl+Z 向前台进程发送的 SIGINT、SIGQUIT 以及 SIGTSTP 信号
#include <signal.h>
// pthread_sigmask() 函数用法与 sigprocmask() 完全一样
int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);
多线程程序中,可通过 pthread_kill() 向同一进程中的某个指定线程发送信号
#include <signal.h>
int pthread_kill(pthread_t thread, int sig);
pthread_sigqueue() 也可向同一进程中的某个指定的线程发送信号
#include <signal.h>
#include <pthread.h>
int pthread_sigqueue(pthread_t thread, int sig, const union sigval value);