Linux高并发服务器开发---笔记3(多线程)

0703

4.0 视频课链接

4.1 项目介绍与环境搭建

4.2 Linux系统编程1、4.3 Linux系统编程2

4.4 多进程

1-9

Linux高并发服务器开发—笔记1

10.进程间通信☆☆☆

Linux高并发服务器开发—笔记2

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(
  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值