守护进程和线程

守护进程和线程🖨

1. 守护进程🐰

1.1 守护进程介绍

Daemon(精灵)进程,是Linux中的后台服务进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。
一般采取以d结尾的名字,如:vsftpd

Linux后台的一些系统服务进程,没有控制终端,不能直接和用户交互。不受用户登录、注销的影响,一直在运行着,他们都是守护进程。
如:预读入缓输出机制的实现;ftp 服务器;nfs 服务器等。

【🎫】守护进程特点
1️⃣ Linux后台服务进程 2️⃣ 独立于控制终端 3️⃣ 周期性的执行某种任务 4️⃣ 不受用户登录和注销的影响 5️⃣ 一般采用d结尾的名字

1.2 进程组和会话

  • 进程组
    • 进程组是一个或者多个进程的集合,每个进程都属于一个进程组,引入进程组是为了进化对进程的管理。当父进程创建子进程的时候,默认子进程与父进程属于同一个进程组

【🥊】进程组ID == 第一个进程ID(组长进程)
如父进程创建了多个子进程,父进程和多个子进程同属于一个组,而且父进程是进程组里的第一个进程,所以父进程就是这个组的组长
组长ID == 父进程ID

【⚠️】kill -SIGKILL -进程组ID(负) 杀死进程组所有进程

【🍀】进程组生存期:从进程创建到最后一个进程离开
只要进程组中有一个进程存在,进程组就存在,与组长进程是否终止无关

  • 会话
    • 一个会话是一个或多个进程组的集合

【🔑】创建会话的进程不能是进程组组长
创建会话的进程成为一个进程的组长进程,同时也成为会话的会长
需要root权限(有的系统不需要)
新创建的会话丢弃原有的控制终端

【🚜】创建新会话
先调用 fork ,父进程终止,子进程调用 setsid 函数

ps ajx 查看进程组ID和会话ID
PPID 父进程ID PID 进程ID PGID 进程组ID SID 会话ID

[xfk@centos COMP_SIGNAL]$ ps ajx
  PPID    PID   PGID    SID TTY       TPGID STAT   UID   TIME COMMAND
     0      1      1      1 ?            -1 Ss       0   0:03 /usr/lib/systemd/sys
     0      2      0      0 ?            -1 S        0   0:00 [kthreadd]
     2      3      0      0 ?            -1 S        0   0:00 [ksoftirqd/0]
  ...

1.3 创建守护进程的模型

第一步:fork 子进程,父进程退出
保证子进程ID不是进程组ID

第二步:子进程调用 setsid 函数创建新会话
该进程成为新会话的首进程,是会话的会长
该进程成为一个新进程组的组长进程,是进程组组长
不受控制终端的影响

第三步:改变当前工作目录 chdir (非必要)

第四步:重设文件掩码(非必要) mode & ~umask
子进程继承父进程的掩码,受到约束。
>>>直接设置 umask(0000);

第五步:关闭文件描述符(非必要)
close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO);

第六步:执行核心操作
守护进程的核心代码逻辑

  1 //创建守护进程
  2 #include <stdio.h>
  3 #include <stdlib.h>
  4 #include <string.h>
  5 #include <sys/types.h>
  6 #include <sys/stat.h>
  7 #include <fcntl.h>
  8 #include <unistd.h>
  9 #include <signal.h>
 10 #include <sys/time.h>
 11 #include <time.h>
 12 
 13 void func(int signo)
 14 {
 15     //打开文件
 16     int fd = open("daemon.log", O_RDWR | O_CREAT | O_APPEND, 0755);
 17     if(fd<0)
 18     {
 19         return;
 20     }
 21 
 22     //获取当前系统时间
 23     time_t t;
 24     time(&t);
 25     char *p = ctime(&t);
 26 
 27     //将时间写入文件
 28     write(fd, p, strlen(p));
 29 
 30     //关闭文件
 31     close(fd);
 32 }
 33 
 34 int main()
 35 {
 36     //创建子进程
 37     pid_t pid = fork();
 38     if(pid<0||pid>0)
 39     {
 40         exit(1);
 41     }
 42 
 43     //创建会话
 44     setsid();
 45 
 46     //修改当前工作目录(非必要)
 47     //chdir("pathname");
 48 
 49     //重设文件掩码
 50     umask(0000);
 51 
 52     //关闭文件描述符
 53     close(STDIN_FILENO);
 54     close(STDOUT_FILENO);
 55     close(STDERR_FILENO);
 56 	//核心操作
 57     //创建信号处理函数
 58     struct sigaction act;
 59     act.sa_handler = func;
 60     act.sa_flags = 0;
 61     sigemptyset(&act.sa_mask);
 62     sigaction(SIGALRM, &act, NULL);
 63 
 64     //设置定时器
 65     struct itimerval tm;
 66     tm.it_interval.tv_sec = 2;
 67     tm.it_interval.tv_usec = 0;
 68     tm.it_value.tv_sec = 3;
 69     tm.it_value.tv_usec = 0;
 70     setitimer(ITIMER_REAL, &tm, NULL);
 71 
 72     while(1)
 73     {
 74         sleep(1);
 75     }
 76     return 0;
 77 }

 >>>>执行结果
 [xfk@centos DAEMON]$ ps ajx | grep ./daemon
     1  13671  13671  13671 ?            -1 Ss    1000   0:00 ./daemon
 [xfk@centos DAEMON]$ tail -f daemon.log
 Tue Jan 24 20:56:36 2023
 Tue Jan 24 20:56:38 2023
 Tue Jan 24 20:56:40 2023
 Tue Jan 24 20:56:42 2023
 ^C

2. 线程⚡️

2.1 什么是线程

轻量级的进程(LWP:light weight process),在Linux环境下线程的本质仍是进程,进程可看成是只有一个线程的进程

  • 进程:拥有独立的地址空间,拥有PCB,相当于独居

  • 线程:有PCB,但没有独立的地址空间,多个线程共享进程空间,相当于合租

【🎳】系统分配资源的最基本单位是:进程
系统调度进程执行的最小单位是:线程
多个子线程和主线程共享一个地址空间,有一个PID,通过使用线程号来区分不同的线程
主线程和子线程执行先后不一定,看谁先抢到cpu资源

ps -Lf pid 查看指定线程的LWP号

[xfk@centos DAEMON]$ ps -Lf 8763
UID         PID   PPID    LWP  C NLWP STIME TTY      STAT   TIME CMD
xfk        8763   8703   8763  0    1 Jan24 pts/1    Ss+    0:00 bash

【🚨】创建进程 fork / 创建线程 pthread_create 底层实现都是调用同一个内核函数 clone
如果复制对方的地址空间,那么就产出一个进程;如果共享对方的地址空间,就产生一个线程

Linux内核是不区分进程和线程的,只在用户层面上进行区分
线程所有操作函数 pthread_* 是库函数,而非系统调用

2.2 线程共享资源/非共享资源

  • 共享资源:
    1️⃣ 文件描述符表
    2️⃣ 每种信号的处理方式
    3️⃣ 当前工作目录
    4️⃣ 用户ID和组ID
    5️⃣ 内存址空间

  • 非共享资源:
    1️⃣ 线程ID
    2️⃣ 处理器现场和栈指针(内核栈)
    3️⃣ 独立的栈空间(用户空间栈)
    4️⃣ errno 变量
    5️⃣ 信号屏蔽字/调度优先级

2.3 线程优缺点

  • 优点
    • 提高程序并发性
    • 开销小
    • 数据通信、共享数据方便
  • 缺点
    • 库函数,不稳定
    • gdb调试、编写困难
    • 对信号支持不好

2.4 线程相关函数

#include <pthread.h>
Compile and link with -pthread
pthread_create函数
  • 函数作用:创建一个新线程
  • 函数原型:
    int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
  • 函数返回值:
    • 成功:返回 0
    • 失败:返回错误号
  • 函数参数:
    • pthread_t :传出参数,保存系统为我们分配好的线程ID
    • attr :通常传NULL,表示使用线程默认属性
    • start_routine :函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束
    • arg :线程主函数执行期间所使用的参数

【🖨】不能使用 perror 函数,取而代之的是 strerror 函数
不能使用 exit/_exit 函数,否则导致整个进程退出

【⚠️】编译时链接库 -lpthread

  1 //创建子线程
  2 #include <stdio.h>
  3 #include <stdlib.h>
  4 #include <string.h>
  5 #include <sys/types.h>
  6 #include <unistd.h>
  7 #include <pthread.h>
  8 
  9 void *threadfunc(void *arg)			//线程执行函数
 10 {
 11     printf("child thread, pid==[%d], id==[%ld]\n", getpid(), pthread_self());
 12 }
 13 
 14 int main()
 15 {
 16     //创建子线程
 17     pthread_t thread;
 18     int ret = pthread_create(&thread, NULL, threadfunc, NULL);
 19     if (ret!=0)
 20     {
 21         printf("pthread_create error, [%s]\n", strerror(ret));
 22         return -1;
 23     }
 24     printf("main thread, pid==[%d], id==[%ld]\n", getpid(), pthread_self());
 25 
 26     sleep(1);
 27     return 0;
 28 }

 >>>>执行结果
 [xfk@centos PTHREAD]$ gcc -o pthread_create pthread_create.c -lpthread
 [xfk@centos PTHREAD]$ ./pthread_create 
 main thread, pid==[17065], id==[140320903141184]
 child thread, pid==[17065], id==[140320894809856]
  1 //创建子线程:参数传递
  2 #include <stdio.h>
  3 #include <stdlib.h>
  4 #include <string.h>
  5 #include <sys/types.h>
  6 #include <unistd.h>
  7 #include <pthread.h>
  8 
  9 struct A
 10 {
 11     int data;
 12     char buf[64];
 13 };
 14 
 15 void *threadfunc(void *arg)
 16 {
 17     //int参数传递
 18     //int n = *(int *)arg;
 19     //printf("n==[%d]", n);
 20     //结构体参数传递
 21     struct A *p = (struct A *)arg;
 22     printf("struct A:\n[%d][%s]\n", p->data, p->buf);
 23     printf("child thread, pid==[%d], id==[%ld]\n", getpid(), pthread_self());
 24 }
 25 
 26 int main()
 27 {
 28     int n = 10;
 29     struct A atest;
 30     memset(&atest, 0x00, sizeof(struct A));
 31     atest.data = 100;
 32     strcpy(atest.buf, "xfk tq");
 33     //创建子线程
 34     pthread_t thread;
 35     //int ret = pthread_create(&thread, NULL, threadfunc, &n);
 36     int ret = pthread_create(&thread, NULL, threadfunc, &atest);
 37     if (ret!=0)
 38     {   
 39         printf("pthread_create error, [%s]\n", strerror(ret));
 40         return -1;
 41     }
 42     printf("main thread, pid==[%d], id==[%ld]\n", getpid(), pthread_self());
 43 
 44     sleep(1);
 45     return 0;
 46 }     
     
 >>>>执行结果
 [xfk@centos PTHREAD]$ gcc -o pthread_create_arg pthread_create_arg.c -lpthread
 [xfk@centos PTHREAD]$ ./pthread_create_arg 
 main thread, pid==[17699], id==[140652003301184]
 struct A:
 [100][xfk tq]
 child thread, pid==[17699], id==[140651994969856]
  1 //循环创建子线程
  2 #include <stdio.h>
  3 #include <stdlib.h>
  4 #include <string.h>
  5 #include <sys/types.h>
  6 #include <unistd.h>
  7 #include <pthread.h>
  8 
  9 void *threadfunc(void *arg)
 10 {
 11     int i = *(int *)arg;
 12     printf("[%d]:child thread, pid==[%d], id==[%ld]\n", i, getpid(), pthread_self());
 13 }
 14 
 15 int main()
 16 {
 17     //循环创建子线程
 18     int i = 0;					
 19     int n = 3;					
 20     int arr[5];					//子线程单独访问独立内存空间,可实现区分
 21     int ret;
 22     pthread_t thread[5];
 23     for(i=0;i<n;i++)
 24     {
 25         arr[i] = i;
 26         int ret = pthread_create(&thread[i], NULL, threadfunc, &arr[i]);
 27         if (ret!=0)
 28         {
 29             printf("pthread_create error, [%s]\n", strerror(ret));
 30             return -1;
 31         }
 32     }
 33     printf("main thread, pid==[%d], id==[%ld]\n", getpid(), pthread_self());
 34 
 35     sleep(1);
 36     return 0;
 37 }

 >>>>执行结果
 [xfk@centos PTHREAD]$ ./pthread_create_loop
 main thread, pid==[11162], id==[140445466715968]
 [0]:child thread, pid==[11162], id==[140445458384640]
 [1]:child thread, pid==[11162], id==[140445449991936]
 [2]:child thread, pid==[11162], id==[140445441599232]

【👓】传递 i 变量无法区分子线程
由于其内存空间只有一块,被所有子线程共享
主线程在一个cpu时间片内完成了所有子线程的创建,而子线程后续才执行,此时共享 i=3 ,使用独立的内存空间可实现区分

pthread_exit函数

在线程中禁止调用 exit 函数,否则会导致整个进程退出,取而代之的是 pthread_exit 函数,这个函数是使一个线程退出
如果主线程调用 pthread_exit 函数也不会使整个进程退出,不影响其他线程的执行

  • 函数作用:将单个线程退出
  • 函数原型:
    void pthread_exit(void *retval);
  • 函数参数:
    • retval 表示线程退出状态,通常传NULL

【⭕️】pthread_exitreturn 返回的指针所指向的内存单元必须是全局的或者是malloc分配的,不能再线程函数的栈上分配
因为当其它线程得到这个返回指针时线程函数已经退出了,栈空间就会被回收

  1 //pthread_exit测试:主线程退出,子线程为退出
  2 #include <stdio.h>
  3 #include <stdlib.h>
  4 #include <string.h>
  5 #include <sys/types.h>
  6 #include <unistd.h>
  7 #include <pthread.h>
  8 
  9 void *threadfunc(void *arg)
 10 {
 11     printf("child thread, pid==[%d], id==[%ld]\n", getpid(), pthread_self());
 12     sleep(100);
 13 }
 14 
 15 int main()
 16 {
 17     //
 18     pthread_t thread;
 19     int ret = pthread_create(&thread, NULL, threadfunc, NULL);
 20     if (ret!=0)
 21     {
 22         printf("pthread_create error, [%s]\n", strerror(ret));
 23         return -1;
 24     }
 25     printf("main thread, pid==[%d], id==[%ld]\n", getpid(), pthread_self());
 26 
 27     pthread_exit(NULL);
 28     sleep(1);
 29     return 0;
 30 }

 >>>>执行结果
 [xfk@centos PTHREAD]$ ps ajx | grep pthread_exit
  8760  13816  13816   8760 pts/0     13816 Zl+   1000   0:00 [pthread_exit] <defunct>
 [xfk@centos PTHREAD]$ ps -Lf 13816
 UID         PID   PPID    LWP  C NLWP STIME TTY      STAT   TIME CMD
 xfk       13816   8760  13816  0    2 23:12 pts/0    Zl+    0:00 [pthread_exit] <defunct>
 xfk       13816   8760  13817  0    2 23:12 pts/0    Sl+    0:00 [pthread_exit] <defunct>

【🏈】主线程退出变成僵尸进程是由于还未回收
解决办法:pthread_join

pthread_join函数
  • 函数作用:阻塞等待线程退出,获取线程退出状态(对应进程中的 waitpid
  • 函数原型:
    int pthread_join(pthread_t thread, void **retval);
  • 函数返回值:
    • 成功:返回 0
    • 失败:返回错误号
  • 函数参数:
    • thread 线程ID
    • retval 存储线程结束状态,整个指针和 pthread_exit 的参数是同一块内存地址
  1 //pthread_join测试:回收子线程
  2 #include <stdio.h>
  3 #include <stdlib.h>
  4 #include <string.h>
  5 #include <sys/types.h>
  6 #include <unistd.h>
  7 #include <pthread.h>
  8 
  9 int g_var = 10;		//线程结束状态(可以是任何数据类型)
 10 
 11 void *threadfunc(void *arg)
 12 {
 13     printf("child thread, pid==[%d], id==[%ld]\n", getpid(), pthread_self());
 14     printf("[%p]\n", &g_var);
 15     pthread_exit(&g_var);
 16 }
 17 
 18 int main()
 19 {
 20     //创建子线程
 21     pthread_t thread;
 22     int ret = pthread_create(&thread, NULL, threadfunc, NULL);
 23     if (ret!=0)
 24     {
 25         printf("pthread_create error, [%s]\n", strerror(ret));
 26         return -1;
 27     }
 28     printf("main thread, pid==[%d], id==[%ld]\n", getpid(), pthread_self());
 29 
 30     //回收子线程
 31     void *p = NULL;
 32     pthread_join(thread, &p);
 33     printf("child exit status:[%d],[%p]\n", *(int *)p, p);
 34 
 35     return 0;
 36 }

 >>>>执行结果
 [xfk@centos PTHREAD]$ ./pthread_join 
 main thread, pid==[14372], id==[140440773224256]
 child thread, pid==[14372], id==[140440764892928]
 [0x60105c]
 child exit status:[10],[0x60105c]
pthread_detach函数

线程分离状态:指定该状态,线程主动与主控线程断开关系
线程结束后,其退出状态不由其它线程获取,而直接自己自动释放(网络、多线程服务器常用)

【🎫】pthread_create 函数第二个参数可设置线程分离
pthread_detach 函数是在创建线程之后调用的

  • 函数作用:实现线程分离
  • 函数原型:
    int pthread_detach(pthread_t thread);
  • 函数返回值:
    • 成功:返回 0
    • 失败:返回错误号
  • 函数参数:thread 线程ID
  1 //pthread_detach测试
  2 #include <stdio.h>
  3 #include <stdlib.h>
  4 #include <string.h>
  5 #include <sys/types.h>
  6 #include <unistd.h>
  7 #include <pthread.h>
  8 
  9 void *threadfunc(void *arg)
 10 {
 11     printf("child thread, pid==[%d], id==[%ld]\n", getpid(), pthread_self());
 12 }
 13 
 14 int main()
 15 {
 16     //创建子线程
 17     pthread_t thread;
 18     int ret = pthread_create(&thread, NULL, threadfunc, NULL);
 19     if (ret!=0)
 20     {
 21         printf("pthread_create error, [%s]\n", strerror(ret));
 22         return -1;
 23     }
 24     printf("main thread, pid==[%d], id==[%ld]\n", getpid(), pthread_self());
 25 
 26     //设置线程分离
 27     pthread_detach(thread);
 28     //子线程分离状态则pthread_join不再阻塞,立即返回 
 29     ret = pthread_join(thread, NULL);
 30     if (ret!=0)
 31     {
 32         printf("pthread_join error:[%s]\n", strerror(ret));
 33     }
 34 
 35     sleep(1);
 36     return 0;
 37 }

 >>>>执行结果
 [xfk@centos PTHREAD]$ ./pthread_detach 
 main thread, pid==[14984], id==[140373116782400]
 pthread_join error:[Invalid argument]
 child thread, pid==[14984], id==[140373108451072]
pthread_cancel函数
  • 函数作用:杀死取消线程(对应进程中的 kill
  • 函数原型:
    int pthread_cancel(pthread_t thread);
  • 函数返回值:
    • 成功:返回 0
    • 失败:返回错误号
  • 函数参数:thread 线程ID

【⏳】线程的取消并不是实时的,而有一定的延迟,需要线程到达某个取消点(检查点)
取消点:是线程检查是否被取消,并按请求进行动作的一个位置,通常一些系统调用具备取消点 ( man 7 pthreads 可查看)
设置取消点: void pthread_testcancel(void);

  1 //pthread_cancel测试
  2 #include <stdio.h>
  3 #include <stdlib.h>
  4 #include <string.h>
  5 #include <sys/types.h>
  6 #include <unistd.h>
  7 #include <pthread.h>
  8 
  9 void *threadfunc(void *arg)
 10 {
 11     while(1)
 12     {
 13         pthread_testcancel();		//设置取消点
 14     }
 15 }
 16 
 17 int main()
 18 {
 19     //创建子线程
 20     pthread_t thread;
 21     int ret = pthread_create(&thread, NULL, threadfunc, NULL);
 22     if (ret!=0)
 23     {
 24         printf("pthread_create error, [%s]\n", strerror(ret));
 25         return -1;
 26     }
 27     printf("main thread, pid==[%d], id==[%ld]\n", getpid(), pthread_self());
 28 
 29     //取消子线程
 30     pthread_cancel(thread);
 31     ret = pthread_join(thread, NULL);
 32     if(ret==0)
 33     {
 34         printf("child cancel\n");
 35     }
 36 
 37     return 0;
 38 }

 >>>>执行结果
 [xfk@centos PTHREAD]$ ./pthread_cancel 
 main thread, pid==[15923], id==[139871012722496]
 child cancel
pthread_equal函数
  • 函数作用:比较两个线程ID是否相等

  • 函数原型:
    int pthread_equal(pthread_t t1, pthread_t t2);

  • 函数返回值:

    • 相等:非 0 值
    • 不相等:0
  • 函数参数:t1 t2 比较的线程ID

【🚩】扩展使用:pthread_t 类型可能在未来变化(比如结构体),为此提供接口

  1 //pthread_equal测试
  2 #include <stdio.h>
  3 #include <stdlib.h>
  4 #include <string.h>
  5 #include <sys/types.h>
  6 #include <unistd.h>
  7 #include <pthread.h>
  8 
  9 void *threadfunc(void *arg)
 10 {
 11     printf("child thread, pid==[%d], id==[%ld]\n", getpid(), pthread_self());
 12 }
 13 
 14 int main()
 15 {
 16     //创建子线程
 17     pthread_t thread;
 18     int ret = pthread_create(&thread, NULL, threadfunc, NULL);
 19     if (ret!=0)
 20     {
 21         printf("pthread_create error, [%s]\n", strerror(ret));
 22         return -1;
 23     }
 24     printf("main thread, pid==[%d], id==[%ld]\n", getpid(), pthread_self());
 25 
 26     sleep(1);
 27     //比较线程ID
 28     if(pthread_equal(pthread_self(), thread)!=0)
 29     {
 30         printf("SAME\n");
 31     }
 32     else
 33     {
 34         printf("NOT SAME\n");
 35     }
 36 
 37     return 0;
 38 }

 >>>>执行结果
 [xfk@centos PTHREAD]$ ./pthread_equal 
 main thread, pid==[16315], id==[140453167179584]
 child thread, pid==[16315], id==[140453158848256]
 NOT SAME

2.5 线程属性

设置线程属性: >>>(pthread_create 第二个参数)

第一步:定义线程属性类型变量
pthread_attr_t attr

第二步:对线程属性变量进行初始化
int pthread_attr_init(pthread_attr_t *attr);

第三步:设置线程为分离属性
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
参数:attr 线程属性
detachstate :分离 PTHREAD_CREATE_DETACHED / 非分离 PTHREAD_CREATE_JOINABLE

第四步:释放线程属性资源
int pthread_attr_destroy(pthread_attr_t *attr);

  1 //设置线程分离属性
  2 #include <stdio.h>
  3 #include <stdlib.h>
  4 #include <string.h>
  5 #include <sys/types.h>
  6 #include <unistd.h>
  7 #include <pthread.h>
  8 
  9 void *threadfunc(void *arg)
 10 {
 11     printf("child thread, pid==[%d], id==[%ld]\n", getpid(), pthread_self());
 12 }
 13 
 14 int main()
 15 {
 16     //定义线程属性类型变量
 17     pthread_attr_t attr;
 18     //对线程属性变量进行初始化
 19     pthread_attr_init(&attr);
 20     //设置线程为分离属性
 21     pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
 22 
 23     //创建子线程
 24     pthread_t thread;
 25     int ret = pthread_create(&thread, &attr, threadfunc, NULL);
 26     if (ret!=0)
 27     {
 28         printf("pthread_create error, [%s]\n", strerror(ret));
 29         return -1;
 30     }
 31     printf("main thread, pid==[%d], id==[%ld]\n", getpid(), pthread_self());
 32 
 33     //释放线程属性资源
 34     pthread_attr_destroy(&attr);
 35     //子线程分离状态则pthread_join不再阻塞,立即返回
 36     ret = pthread_join(thread, NULL);
 37     if (ret!=0)
 38     {
 39         printf("pthread_join error:[%s]\n", strerror(ret));
 40     }
 41 
 42     sleep(1);
 43     return 0;
 44 }

 >>>>执行结果
 [xfk@centos PTHREAD]$ ./pthread_attr 
 main thread, pid==[9505], id==[139874090227520]
 pthread_join error:[Invalid argument]
 child thread, pid==[9505], id==[139874081896192]

3. 线程同步初阶📡

线程同步,指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时其它线程为保证数据一致性,不能调用该功能。

【💊 TEST】 两个线程计数,共享同一个全局变量,分别计数5000
RESULT >>> 10000

  1 //测试两个线程计数,共享同一个全局变量
  2 #include <stdio.h>
  3 #include <stdlib.h>
  4 #include <string.h>
  5 #include <sys/types.h>
  6 #include <unistd.h>
  7 #include <pthread.h>
  8 
  9 #define NUM 5000
 10 
 11 int num = 0;
 12 
 13 void *threadfunc1(void *arg)
 14 {
 15     int i = 0;
 16     int n;
 17     for(i=0;i<NUM;i++)
 18     {
 19         n = num;
 20         n++;
 21         num = n;
 22     }
 23 }
 24 
 25 void *threadfunc2(void *arg)
 26 {
 27     int i = 0;
 28     int n;
 29     for(i=0;i<NUM;i++)
 30     {
 31         n = num;
 32         n++;
 33         num = n;
 34     }
 35 }
 36 
 37 int main()
 38 {
 39     
 40     pthread_t thread1;
 41     int ret = pthread_create(&thread1, NULL, threadfunc1, NULL);
 42     if (ret!=0)
 43     {
 44         printf("pthread_create error, [%s]\n", strerror(ret));
 45         return -1;
 46     }
 47 
 48     pthread_t thread2;
 49     ret = pthread_create(&thread2, NULL, threadfunc2, NULL);
 50     if (ret!=0)
 51     {
 52         printf("pthread_create error, [%s]\n", strerror(ret));
 53         return -1;
 54     }
 55 
 56     pthread_join(thread1, NULL);
 57     pthread_join(thread2, NULL);
 58 
 59     printf("num==[%d]\n", num);
 60 
 61     return 0;
 62 }

 >>>>执行结果(可能出现少数的现象)
 [xfk@centos PTHREAD]$ ./pthread_lock 
 num==[9996]

【🐞】资源共享
随机调度(线程按cpu时间片运行,线程之间来回切换)
线程间缺乏必要的同步机制

【🔐】互斥锁:线程A和线程B共同访问共享资源,当线程A想访问共享资源的时候,要先获得锁
如果锁被占用,则阻塞等待
如果锁未被占用,则加锁操作共享资源

3.1 互斥锁相关函数

#include <pthread.h>

【🛒】pthread_mutex_t 类型(struct)
pthread_mutex_t mutex; 该变量只有两种取值1/0

pthread_mutex_init函数
  • 函数作用:初始化一个互斥锁(初值可看作1)
  • 函数原型:
    int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
  • 函数返回值:
    • 成功:返回 0
    • 失败:返回错误号
  • 函数参数:
    • mutex 传出参数
    • attr 互斥锁属性,通常传NULL,默认属性(线程间共享)

【🔑】restrict 关键字
只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过该指针完成

【➕】mutex 变量的初始化方式
静态初始化(全局或static修饰):pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
动态初始化(局部):pthread_mutex_init(&mutex, NULL);

pthread_mutex_destroy函数
  • 函数作用:销毁一个互斥锁

  • 函数原型:
    int pthread_mutex_destroy(pthread_mutex_t *mutex);

  • 函数返回值:

    • 成功:返回 0
    • 失败:返回错误号
  • 函数参数:mutex 互斥锁变量

pthread_mutex_lock函数
  • 函数作用:对互斥锁加锁,可理解为 mutex--
  • 函数原型:
    int pthread_mutex_lock(pthread_mutex_t *mutex);
  • 函数返回值:
    • 成功:返回 0
    • 失败:返回错误号
  • 函数参数:mutex 互斥锁变量
pthread_mutex_unlock函数
  • 函数作用:对互斥锁解锁,可理解为 mutex++

  • 函数原型:
    int pthread_mutex_unlock(pthread_mutex_t *mutex);

  • 函数返回值:

    • 成功:返回 0
    • 失败:返回错误号
  • 函数参数:mutex 互斥锁变量

pthread_mutex_trylock函数
  • 函数作用:尝试加锁
  • 函数原型:
    int pthread_mutex_trylock(pthread_mutex_t *mutex);
  • 函数返回值:
    • 成功:返回 0
    • 失败:返回错误号
  • 函数参数:mutex 互斥锁变量

3.2 互斥锁:加锁/解锁

【🎫】互斥锁使用步骤
1.定义互斥锁变量 pthread_mutex_t mutex;
2.在main函数中初始化互斥锁 pthread_mutex_init(&mutex, NULL);
3.在共享资源操作上下加锁/解锁
pthread_mutex_lock(&mutex);
//共享资源操作代码//
pthread_mutex_unlock(&mutex);
4.在main函数中销毁互斥锁 pthread_mutex_destroy(&mutex);

  1 //互斥锁测试
  2 #include <stdio.h>
  3 #include <stdlib.h>
  4 #include <string.h>
  5 #include <sys/types.h>
  6 #include <unistd.h>
  7 #include <pthread.h>
  8 
  9 #define NUM 5000
 10 
 11 int num = 0;
 12 
 13 //定义互斥锁变量
 14 pthread_mutex_t mutex;
 15 
 16 void *threadfunc1(void *arg)
 17 {
 18     int i = 0;
 19     int n;
 20     for(i=0;i<NUM;i++)
 21     {
 22         //加锁
 23         pthread_mutex_lock(&mutex);
 24         n = num;
 25         n++;
 26         num = n;
 27         //解锁
 28         pthread_mutex_unlock(&mutex);
 29     }
 30 }
 31 
 32 void *threadfunc2(void *arg)
 33 {
 34     int i = 0;
 35     int n;
 36     for(i=0;i<NUM;i++)
 37     {
 38         pthread_mutex_lock(&mutex);
 39         n = num;
 40         n++;
 41         num = n;
 42         pthread_mutex_unlock(&mutex);
 43     }
 44 }
 45 
 46 int main()
 47 {
 48     //初始化互斥锁
 49     pthread_mutex_init(&mutex, NULL);
 50 
 51     pthread_t thread1;
 52     int ret = pthread_create(&thread1, NULL, threadfunc1, NULL);
 53     if (ret!=0)
 54     {
 55         printf("pthread_create error, [%s]\n", strerror(ret));
 56         return -1;
 57     }
 58 
 59     pthread_t thread2;
 60     ret = pthread_create(&thread2, NULL, threadfunc2, NULL);
 61     if (ret!=0)
 62     {
 63         printf("pthread_create error, [%s]\n", strerror(ret));
 64         return -1;
 65     }
 66 
 67     pthread_join(thread1, NULL);
 68     pthread_join(thread2, NULL);
 69 
 70     //销毁互斥锁
 71     pthread_mutex_destroy(&mutex);
 72 
 73     printf("num==[%d]\n", num);
 74 
 75     return 0;
 76 }

 >>>>执行结果(不会出错,但执行效率变低)
 [xfk@centos PTHREAD]$ ./pthread_lock 
 num==[10000]

【🏧】原子操作:该操作要么不执行,要么就完成(互斥锁是原子操作的模拟)


✍️ 小方块xfk

📅 写于 2023年1月

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值