0703
第4章 项目制作与技能提升
4.0 视频课链接
4.1 项目介绍与环境搭建
4.2 Linux系统编程1、4.3 Linux系统编程2
4.4 多进程
1-9
10.进程间通信☆☆☆
4.5 多线程
(视频课从01:21:28开始)
4.5.1 线程的概念
线程:轻量级进程LWP
与进程(process)类似,线程(thread)是允许应用程序并发执行多个任务的一种机制。一个进程可以包含多个线程。同一个程序中的所有线程均会独立执行相同程序,且共享同一份全局内存区域,其中包括初始化数据段、未初始化数据段,以及堆内存段。(传统意义上的 UNIX 进程只是多线程程序的一个特例,该进程只包含一个线程)
进程是 CPU 分配资源的最小单位,线程是操作系统调度执行的最小单位。
线程是轻量级的进程(LWP:Light Weight Process),在 Linux 环境下线程的本质仍是进程。
查看指定进程的 LWP 号:ps –Lf pid
进程 和 线程 的区别
进程间的信息难以共享。由于除去只读代码段外,父子进程并未共享内存,因此必须采用一些进程间通信方式,在进程间进行信息交换。
调用 fork() 来创建进程的代价相对较高,即便利用写时复制技术,仍然需要复制诸如内存页表和文件描述符表之类的多种进程属性,这意味着 fork() 调用在时间上的开销依然不菲。
线程之间能够方便、快速地共享信息。只需将数据复制到共享(全局或堆)变量中即可。
创建线程比创建进程通常要快 10 倍甚至更多。线程间是共享虚拟地址空间的,无需采用写时复制来复制内存,也无需复制页表。
线程之间共享和非共享资源
共享资源:
- 进程 ID 和父进程 ID
- 进程组 ID 和会话 ID
- 用户 ID 和 用户组 ID
- 文件描述符表
- 信号处置
- 文件系统的相关信息:文件权限掩码
umask
(抹去一些权限)、当前工作目录 - 虚拟地址空间(除栈、.text外)
非共享资源:
- 线程 ID
- 信号掩码
- 线程特有数据
- error 变量
- 实时调度策略和优先级
- 栈,本地变量和函数的调用链接信息
NPTL(编译的时候后面加 -pthread
表示线程库)
当 Linux 最初开发时,在内核中并不能真正支持线程。但是它的确可以通过 clone() 系统调用将进程作为可调度的实体。这个调用创建了调用进程(calling process)的一个拷贝,这个拷贝与调用进程共享相同的地址空间。
LinuxThreads 项目使用这个调用来完成在用户空间模拟对线程的支持。不幸的是,这种方法有一些缺点,尤其是在信号处理、调度和进程间同步等方面都存在问题。另外,这个线程模型也不符合 POSIX 的要求。
要改进 LinuxThreads,需要内核的支持,并且重写线程库。有两个相互竞争的项目开始来满足这些要求。一个包括 IBM 的开发人员的团队开展了 NGPT(Next-Generation POSIX Threads)项目。同时,Red Hat 的一些开发人员开展了 NPTL 项目。NGPT 在 2003 年中期被放弃了,把这个领域完全留给了NPTL。
NPTL,或称为 Native POSIX Thread Library,是 Linux 线程的一个新实现,它克服了 LinuxThreads的缺点,同时也符合 POSIX 的需求。与 LinuxThreads 相比,它在性能和稳定性方面都提供了重大的改进。
查看当前 pthread 库版本:getconf GNU_LIBPTHREAD_VERSION
root@VM-16-2-ubuntu:/home/reus/牛客网-Linux高并发服务器开发# getconf GNU_LIBPTHREAD_VERSION
NPTL 2.31
编译的时候指定库:g++ a.c -l pthread
或者 g++ a.c -pthread
4.5.2 线程操作函数
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);//创建一个子线程
pthread_t pthread_self(void);//获取当前的线程号
int pthread_equal(pthread_t t1, pthread_t t2);
void pthread_exit(void *retval);//线程退出,就不会影响到其他的线程了
int pthread_join(pthread_t thread, void **retval);//线程回收
int pthread_detach(pthread_t thread);//线程分离
int pthread_cancel(pthread_t thread);//线程取消
其中pthread_equal()函数:
int pthread_equal(pthread_t t1, pthread_t t2);
功能:比较两个线程ID是否相等
不同的操作系统,pthread_t类型的实现不一样,有的是无符号的长整型,有的是使用结构体去实现的。
①pthread_create()函数 — 创建一个子线程
一般情况下,main函数所在的线程我们称之为主线程(main线程),其余创建的线程称之为子线程。
程序中默认只有一个进程,fork()调用函数,就有2个进程;
程序中默认只有一个线程,调用pthread_create()函数,就有2个线程。
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
功能:创建一个子线程
参数:
- thread:传出参数,线程创建成功后,子线程的线程ID被写到该变量中。
- attr : 设置线程的属性,一般使用默认值NULL
- start_routine : 函数指针,这个函数是子线程需要处理的逻辑代码
- arg : 给第三个参数使用,传参
返回值:
成功:0
失败:返回错误号。这个错误号和之前errno不太一样。
获取错误号的信息: char * strerror(int errnum);
示例:
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
void * callback(void * arg) {
printf("child thread...\n");
printf("arg value: %d\n", *(int *)arg);
return NULL;
}
int main() {
pthread_t tid;
int num = 10;
// 创建一个子线程
int ret = pthread_create(&tid, NULL, callback, (void *)&num);
if(ret != 0) {
char * errstr = strerror(ret);
printf("error : %s\n", errstr);
}
for(int i = 0; i < 5; i++) {
printf("%d\n", i);
}
sleep(1);
return 0; // exit(0);
}
编译运行:(记得加-pthread
)
root@VM-16-2-ubuntu:/home/reus/牛客网-Linux高并发服务器开发/04-多线程/01# gcc pthread_create.c -pthread
root@VM-16-2-ubuntu:/home/reus/牛客网-Linux高并发服务器开发/04-多线程/01# ./a.out
0
1
2
3
4
child thread...
arg value: 10
root@VM-16-2-ubuntu:/home/reus/牛客网-Linux高并发服务器开发/04-多线程/01#
☆☆☆注意:子线程和主线程也是抢占资源
把程序中主线程执行的内容:循环次数从5改成30,然后多执行几次,就可以得出结论:子线程和主线程也是抢占资源
多运行几次:
②pthread_exit()函数、pthread_self()函数 — 终止一个线程、获取当前的线程的线程ID
①pthread_exit()函数
#include <pthread.h>
void pthread_exit(void *retval);
功能:终止一个线程,在哪个线程中调用,就表示终止哪个线程;
参数:retval:需要传递一个指针,作为一个返回值,可以在pthread_join()
中获取到。
②pthread_self()函数:获取当前的线程的线程ID
pthread_t pthread_self(void);
示例:
主线程打印5个数,子线程打印190个数,当主线程执行完时,子线程还没执行完,但由于主线程结束了,整个进程也就结束了,因此子线程也就结束了(见结果1),所以就需要让主线程退出pthread_exit(NULL);
这样它就不会影响其他正常运行的线程,子线程也就能正常打印完它的内容(见结果2);当所有的线程都执行完之后,整个进程才会结束。
#include <stdio.h>
#include <pthread.h>
#include <string.h>
void * callback(void * arg) {
printf("child thread id : %ld\n", pthread_self());
for(int i = 10; i < 200; i++) {
printf("j = %d\n", i);
}
return NULL; //相当于pthread_exit(NULL); 即子线程执行完之后也退出,不会影响其他正常运行的线程
}
int main() {
// 创建一个子线程
pthread_t tid;
int ret = pthread_create(&tid, NULL, callback, NULL);
if(ret != 0) {
char * errstr = strerror(ret);
printf("error : %s\n", errstr);
}
// 主线程
for(int i = 0; i < 5; i++) {
printf("i = %d\n", i);
}
printf("tid : %ld, main thread id : %ld\n", tid ,pthread_self());
// 让主线程退出,当主线程退出时,不会影响其他正常运行的线程。
pthread_exit(NULL);//退出线程
printf("main thread exit\n");//有了上面那句,这行就不会执行了
return 0; // exit(0);//退出进程的意思
}
编译运行:(记得加-pthread
)
(结果1:主线程一结束,子线程也就结束了,根本没打印完190个数)
root@VM-16-2-ubuntu:/home/reus/牛客网-Linux高并发服务器开发/04-多线程/01# gcc pthread_exit.c -pthread
root@VM-16-2-ubuntu:/home/reus/牛客网-Linux高并发服务器开发/04-多线程/01# ./a.out
i = 0
i = 1
i = 2
i = 3
i = 4
tid : 140000903485184, main thread id : 140000903489344
main thread exit
child thread id : 140000903485184
j = 1root@VM-16-2-ubuntu:/home/reus/牛客网-Linux高并发服务器开发/04-多线程/01#
(结果2:子线程能正常打印完它该打印的内容,子线程执行完之后return NULL;
// 其实相当于pthread_exit(NULL);
这样它也不会影响别的线程;由于主线程执行pthread_exit(NULL);
之后就退出了,所以它后面那句printf("main thread exit\n");
也就不会执行了)
child thread id : 140364587890432
j = 10
j = 11
...
...
...
j = 190
j = 191
j = 192
j = 193
j = 194
j = 195
j = 196
j = 197
j = 198
j = 199
i = 0
i = 1
i = 2
i = 3
i = 4
tid : 140364587890432, main thread id : 140364587894592
root@VM-16-2-ubuntu:/home/reus/牛客网-Linux高并发服务器开发/04-多线程/01#
③pthread_join()函数 — 和一个已经终止的线程进行连接,回收子线程的资源
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
功能:和一个已经终止的线程进行连接,回收子线程的资源;一般在主线程中使用;
这个函数是阻塞函数,调用一次只能回收一个子线程;
参数:
- thread:需要回收的子线程的ID
- retval: 接收子线程退出时的返回值
返回值:0 : 成功; 非0 : 失败,返回的错误号
示例:
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
int value = 10;//全局变量
void * callback(void * arg) {
printf("child thread id : %ld\n", pthread_self());
sleep(3);
//return NULL; //相当于pthread_exit(NULL); 即子线程执行完之后也退出,不会影响其他正常运行的线程
//int value = 10; // 局部变量
pthread_exit((void *)&value); // return (void *)&value;
}
int main() {
// 创建一个子线程
pthread_t tid;
int ret = pthread_create(&tid, NULL, callback, NULL);
if(ret != 0) {
char * errstr = strerror(ret);
printf("error : %s\n", errstr);
}
// 主线程
for(