什么是线程?
Linux 中的线程被称为“轻量级进程”(Lightweight Process,LWP),它是在进程内部运行的一种“子进程”。与传统的进程不同,线程共享相同的虚拟地址空间和其他资源,例如打开的文件、信号处理程序和用户 ID 等。
线程具有哪些特点
- 轻量级:
因为线程与父进程共享资源,所以创建和销毁线程的开销要比创建和销毁进程的开销要小得多。 - 调度:
Linux 线程是由内核进行调度的,线程的调度是基于调度策略和优先级来完成的。常见的调度策略包括 Round Robin、FIFO 和实时调度等。 - 同步:
由于线程共享同一进程的地址空间,所以线程之间可以通过共享变量进行通信。Linux 提供了许多同步机制,例如信号量、互斥量和条件变量等,以确保线程之间的同步和互斥。 - 线程安全:
Linux 系统提供了一些线程安全的库和函数,例如 pthread 库,这些库和函数可以在多线程程序中使用,避免竞态条件和其他线程安全问题。 - 调试:
Linux 系统提供了一些工具来调试多线程程序,例如 gdb 调试器和 strace 工具。
线程与进程有什么区别
简单的说,进程是程序资源分配的最小单位,线程是程序执行的最小单位。
- 资源共享:
进程是系统分配资源的基本单位,每个进程都有自己独立的地址空间、文件描述符、信号处理等资源,不同进程之间的资源不共享。而线程则共享同一个进程的地址空间和资源,它们之间可以相互访问共享变量,共享同一进程的文件和打开的网络连接等。 - 创建销毁开销:
创建和销毁进程的开销比创建和销毁线程的开销要大。因为每个进程都有自己独立的地址空间和系统资源,创建一个新的进程需要操作系统分配新的内存空间、建立内核数据结构等,而线程则共享其所属进程的资源,因此创建和销毁线程的开销要比创建和销毁进程的开销要小得多。 - 上下文切换:
线程之间的上下文切换比进程之间的上下文切换要快得多。因为线程共享相同的地址空间和资源,切换时只需要保存和恢复少量的寄存器状态即可,而进程之间的上下文切换需要保存和恢复更多的状态信息,比如虚拟内存空间等。 - 并发性和资源竞争
线程之间的并发性比进程之间的并发性更高,因为它们共享同一进程的地址空间和资源,同时也会导致线程之间发生资源竞争的问题,例如竞争共享变量的访问权等。而进程之间的并发性相对较低,因为它们彼此独立。
如何创建一个线程
Talk is cheap,直接上代码!
#include <iostream>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
using namespace std;
void *th_fun(void *arg)
{
int *p = (int *)arg;
printf("thread PID = %d\n",getpid());//获取本进程的ID
printf("thread pthread ID = %d\n",pthread_self());//获取函数自己的ID
printf("thread PARAMETER = %d\n",*p);//打印传入的参数
}
int main(void)
{
pthread_t pid;//线程ID
void *tret;
int n=10;
pthread_create(&pid,NULL,th_fun,(void*)&n);//pid 线程ID,传出参数
pthread_join(pid, &tret);//主线程阻塞等待子线程的终止
printf("MAIN pthread ID = %x\n",pthread_self());//主控线程的ID号
printf("MAIN create pthread ID = %x\n",pid);//线程的ID
printf("MAIN PID = %d\n",getpid());//进程ID
return 0;
}
解读
主函数通过pthread_create函数创建一个线程,线程的函数名为th_fun,同时有一个pid参数代表线程的ID号,通过pthread_t类型定义。这里在pthread_create最后一个参数中,用了一个n变量负责传入一个数据给线程函数,然后在线程函数中打印出来。
pthread_join负责将线程设置为分离态,负责回收线程的资源。
主函数中分别打印了三个内容,主进程的线程的ID号,创建的新线程的ID号,主进程的ID号。
th_fun内容则简单的多,这个新创建的执行线程只负责打印三个内容:创建本线程的进程ID号,本线程自己的线程ID号,传入的arg参数。
输出
thread PID = 14420
thread pthread ID = 2
thread PARAMETER = 10
MAIN pthread ID = 1
MAIN create pthread ID = 2
MAIN PID = 14420
这里主进程的线程ID号为1,新创建的线程ID为2,该主函数执行时创建的进程ID为14420。
先打印出来thread线程的内容:因为线程是由main函数中创建的,所以进程ID同样为14420。新创建的线程ID和主函数中获取的线程ID一样为2,参数10是通过pthread_create的最后一个参数传进来的,这里要注意最后一个参数的类型为 void* 型,所以在赋值的时候也要将变量转换成 void* 型。
线程原语
实际上和线程有关的函数特别多,通过man命令可以查看到有哪些线程原语:
root@ubuntu:/home# man -k pthread
pthread_attr_destroy (3) - initialize and destroy thread attributes object
pthread_attr_getaffinity_np (3) - set/get CPU affinity attribute in thread attributes object
pthread_attr_getdetachstate (3) - set/get detach state attribute in thread attributes object
pthread_attr_getguardsize (3) - set/get guard size attribute in thread attributes object
pthread_attr_getinheritsched (3) - set/get inherit-scheduler attribute in thread attributes object
pthread_attr_getschedparam (3) - set/get scheduling parameter attributes in thread attributes object
pthread_attr_getschedpolicy (3) - set/get scheduling policy attribute in thread attributes object
pthread_attr_getscope (3) - set/get contention scope attribute in thread attributes object
pthread_attr_getstack (3) - set/get stack attributes in thread attributes object
pthread_attr_getstackaddr (3) - set/get stack address attribute in thread attributes object
pthread_attr_getstacksize (3) - set/get stack size attribute in thread attributes object
pthread_attr_init (3) - initialize and destroy thread attributes object
pthread_attr_setaffinity_np (3) - set/get CPU affinity attribute in thread attributes object
pthread_attr_setdetachstate (3) - set/get detach state attribute in thread attributes object
pthread_attr_setguardsize (3) - set/get guard size attribute in thread attributes object
pthread_attr_setinheritsched (3) - set/get inherit-scheduler attribute in thread attributes object
pthread_attr_setschedparam (3) - set/get scheduling parameter attributes in thread attributes object
pthread_attr_setschedpolicy (3) - set/get scheduling policy attribute in thread attributes object
pthread_attr_setscope (3) - set/get contention scope attribute in thread attributes object
pthread_attr_setstack (3) - set/get stack attributes in thread attributes object
pthread_attr_setstackaddr (3) - set/get stack address attribute in thread attributes object
pthread_attr_setstacksize (3) - set/get stack size attribute in thread attributes object
pthread_cancel (3) - send a cancellation request to a thread
...
由于篇幅限制,后面的一部分就省略掉了,读者可以自行去查看。
那我们这里用到的几个和线程有关的线程原语有:
pthread_create
这个函数负责的是创建线程,通过man命令可以获取如何使用该函数。
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
分别解释一下变量的含义:
- pthread_t *thread:传递一个pthread_t变量地址进来,用于保存新线程的tid(线程ID)
- const pthread_attr_t *attr:线程属性设置,如使用默认属性,则传NULL
- void *(*start_routine) (void *):函数指针,指向新线程应该加载执行的函数模块
- void *arg:指定线程将要加载调用的那个函数的参数
- 返回值:成功返回0,失败返回错误号。以前学过的系统函数都是成功返回0,失败返回-1,而错误号保存在全局变量errno中,而pthread库的函数都是通过返回值返回错误号,虽然每个线程也都有一个errno,但这是为了兼容其它函数接口而提供的,pthread库本身并不使用它,通过返回值返回错误码更加清晰
- attr参数表示线程属性,本节不深入讨论线程属性,所有代码例子都传NULL给attr参数,表示线程属性取缺省值,感兴趣的读者可以参考[APUE2e]
pthread_join
调用该函数的线程将挂起等待,直到id为thread的线程终止。也就是在子线程调用了pthread_join()方法后面的代码,只有等到子线程结束了才能执行。
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
- pthread_t thread:回收线程的tid
- void **retval:接收退出线程传递出的返回值
- 返回值:成功返回0,失败返回错误号
注意,这里其实还涉及到与pthread_detach()函数的区别对比。在后续的文章再加以对比说明。