C中进程与线程的基本使用(结合代码讲解)
Posted 我要出家当道士
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C中进程与线程的基本使用(结合代码讲解)相关的知识,希望对你有一定的参考价值。
目录
(6)example(pthread_join主线程阻塞等待后释放资源)
(7)example(pthread_detach子线程结束后自动释放资源)
零、前言
一个简单的总结,内容也比较简单,不是很完整,其他的待日后补充,比如进程间的数据通讯方式、多线程的其他锁的实现和条件变量等等。文章中可能出现内容有异议的,欢迎评论区讨论。下面的内容每部分都结合了demo进行介绍,希望你能从中受益。文章中的内容主要来源于 《unix网络编程卷一》。
一、进程
1、基本介绍与基本使用
使用fork函数创建了一个新的进程,新进程(子进程)与原有的进程(父进程)一模一样。子进程和父进程使用相同的代码段;子进程拷贝了父进程的堆栈段和数据段。子进程一旦开始运行,它复制了父进程的一切数据,各自运行,互不影响。而且由于子进程复制了父进程的堆栈段,所以子进程也复制了父进程的代码执行进度。
#include <unistd.h>
pid_t fork(void);
fork 函数返回两个值,对于调用进程(父进程)返回新派生进程的进程 ID 号;对于子进程返回 0。使用 getpid 函数可以查看当前进程的 ID 号。
子进程可以通过 getppid 函数来获取父进程的进程 ID 号;相反创建子进程后父进程无法再获得子进程的 ID号,唯一的方法就是在调用 fork 函数的时候保存返回值。父进程调用 fork 函数之前打开的所有描述符在 fork 函数返回后由子进程分享。
进程间相互独立,互不影响。每个进程都有自己的地址空间,所以可能会出现,不同进程的变量虚拟地址相同的现象,其实他们指向的物理地址不同。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int ii=10;
int main()
int jj=20;
if (fork()>0)
ii=11;jj=21; sleep(1); printf("父进程:ii=%d,jj=%d\\n",ii,jj);
else
ii=12;jj=22; sleep(1); printf("子进程:ii=%d,jj=%d\\n",ii,jj);
/*
ffy@ffy:~/桌面/work/code$ ./a.out
父进程:ii=11,jj=21
子进程:ii=12,jj=22
*/
2、僵尸进程的产生与解决
(1)原因
在linux系统中可以通过 top 这个工具查看僵尸进程,如下图右上角的 zombie 即为僵尸进程的数量。
a. 在子进程退出前父进程先退出,则系统会让init进程接管子进程;
b. 当子进程先于父进程终止,而父进程又没有调用wait函数等待子进程结束,子进程进入僵尸状态,并且会一直保持下去除非主进程退出。子进程处于僵尸状态时,内核只保存该进程的一些必要信息以备父进程所需。此时子进程始终占用着资源,同时也减少了系统可以创建的最大进程数;
c. 如果子进程先于父进程终止,且父进程调用了 wait 或 waitpid 函数,则父进程会等待子进程结束(阻塞等待)。
(2)解决办法
a. 在Linux下,可以简单地将 SIGCHLD 信号的操作设为 SIG_IGN,这样当子进程结束时就不会称为僵尸进程。
signal(SIGCHLD,SIG_IGN); // 忽略子进程退出的信号,避免产生僵尸进程
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char const *argv[])
pid_t pid = fork();
signal(SIGCHLD,SIG_IGN);
if (pid == 0)
sleep(1);
else
sleep(20)
return 0;
b. 主进程可以通过调用 pid_t wait(int *statloc) 来阻塞的等待子进程的退出。wait 函数将子进程终止时传递的参数值保存到 statloc 所指向的内存空间。然后通过宏 WIFEXITED(status) 确定子进程是否正常终止,通过宏 WEXITSTATUS(status) 获得子进程的返回值。
pid_t wait(int *statloc)
// statloc = &status
WIFEXITED(status)
WEXITSTATUS(status)
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char const *argv[])
pid_t pid = fork();
if (pid == 0)
sleep(15);
return 3;
else
int status;
pid = wait(&status);
printf("child process exited\\n");
if (WIFEXITED(status))
printf("child process return : %d\\n", WEXITSTATUS(status));
return 0;
c. 使用 waitpid,例如:waitpid(-1, &status, WNOHANG) ; -1 表示可以等待任意子进程终止,WNOHANG 表示子进程没有终止也不会进入阻塞状态,而是返回0并退出函数。
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char const *argv[])
pid_t pid = fork();
if (pid == 0)
sleep(15);
return 3;
else
int status;
while (!waitpid(-1, &status, WNOHANG))
printf("child process in running\\n");
printf("do someting\\n");
printf("child process exited\\n");
if (WIFEXITED(status))
printf("child process return : %d\\n", WEXITSTATUS(status));
return 0;
二、线程
1、进程的不足
(1)、不断的生成子进程占用大量资源,因为每个子进程都要把父进程的内存映像复制到子进程中去,并在子进程中复制所有的描述符等。也可以说子进程和父进程完全一样,连程序的执行进度都完全一样,拥有独立的内存空间。
(2)、子进程生成后会复制父进程的所有数据,包括调用fork之前的数据。所以父进程向尚未创建的子进程传递信息很容易,但子进程向父进程传递返回消息十分的吃力,需要借助进程间通讯机制。
2、线程的基本介绍
线程是轻量级的进程,创建速度更快。同一个进程内的线程共享相同的全局内存,这使得线程间共享信息十分方便,但要面临同步的问题。
下面是线程共享与独有的资源。
共享 | 独有 |
进程指令 | 线程ID |
描述符 | 栈 |
信号与信号处理函数 | 寄存器集合(程序计数器和栈) |
用户ID与组ID | errno |
大多数数据 | 信号掩码 |
优先级 |
3、线程的五个基本操作
(1)创建线程
#include <pthread.h>
int pthread_create(pthread_t *tid, const pthread_attr_t *attr,
void *(*func)(void *), void *arg);
程序正常启动后会自动创建主线程,其余线程才由 pthread_create 创建。每个线程由线程ID(thread ID)标识,其数据类型是 pthread_t(unsigned int)。
每个线程都有很多属性:优先级、初始栈大小、是否是守护进程等,可以通过 attr 这个参数传递,默认情况下传递空指针。
最后指导线程的执行函数 func 和该函数的唯一调用参数 arg,如果需要传递多个参数可以打包成结构体传值。func 传入参数和返回值都是通用指针(void *)。
pthread_create 成功创建线程返回 0,不成功返回 非0(正数)。
(2)关闭线程
线程退出的两种方式,一种是子线程执行函数的退出(隐式、显示);另一种是主线程的退出(程序进程结束,其附属的线程自然结束)。
子线程的退出分为显示和隐式退出,隐式退出即线程的执行函数执行 return 返回;显示的退出即调用 pthread_exit。
void pthread_exit(void *status);
(3)获取自身线程的 ID
pthread_t pthread_self(void)
(4)阻塞等待线程的退出并释放资源
通过给定线程 ID,阻塞的等待指定线程的终止,并释放该线程的资源。例如主线程阻塞的等待子线程的关闭,待子线程退出后释放其资源。
#include <pthread.h>
int pthread_join(pthread_t *tid, void **status);
(5)子线程脱离
pthread_detach 函数,unix网络编程中解释是变为脱离状态(detach),脱离后该线程执行完毕会自动释放资源。如果是可汇合状态(joinable),该线程结束后需要另一个线程执行 pthread_join 来释放其资源。
线程有两个状态:joinable 状态和 detach 状态。这个状态可以在 pthread_create 创建线程时设置,默认是 joinable。也就是默认需要你执行 pthread_join() 函数来阻塞的等待子线程的退出,以释放其资源。或者你可以在子线程中使用 pthread_detach(pthread_self()) 函数来改变自身线程的状态为 detach,这样当子线程执行结束后会自动的释放资源。
(6)example(pthread_join主线程阻塞等待后释放资源)
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void * print(void *arg)
pthread_t id = pthread_self();
printf("pthread id : %lu\\n", id);
int start = *(int *)arg;
for (int i = start; i < start + 10; i++)
sleep(1);
printf("%d \\n", i);
// 主动终止
if ( 0 == i % 7)
pthread_exit(NULL);
return NULL;
int main(void)
pthread_t tid1, tid2;
// 定义线程 ID,创建线程,并指定执行函数与参数。
int start1 = 1;
pthread_create(&tid1, NULL, print, &start1);
int start2 = 10;
pthread_create(&tid2, NULL, print, &start2);
// 主线程阻塞的等待线程的终止, 即两个线程都执行完毕后主线程执行之后的步骤。
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
(7)example(pthread_detach子线程结束后自动释放资源)
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void * print(void *arg)
pthread_detach(pthread_self());
int start = *(int *)arg;
for (int i = start; i < start + 10; i++)
sleep(1);
printf("%d \\n", i);
if ( 0 == i % 7)
pthread_exit(NULL);
return NULL;
int main(void)
pthread_t tid1, tid2;
int start1 = 1;
pthread_create(&tid1, NULL, print, &start1);
int start2 = 10;
pthread_create(&tid2, NULL, print, &start2);
sleep(10);
printf("over\\n");
return 0;
4、多线程的数据共享
为了保护多线程共享的数据,可以使用互斥锁。任何线程使用共享的数据时必须持有该锁(上锁),其他线程再试图上锁时会被阻塞,直到该数据被持有线程使用完后解锁。
互斥锁的类型是 pthread_metux_t,互斥锁变量存在两种初始化方式:静态分配和动态分配。
静态分配
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZer;
动态分配
pthread_mutex_init(pthread_mutex_t *mutex,
const pthread_mutex_attr_t *mutexattr);
// mutexattr用于指定锁的属性,默认是普通锁
(1)互斥锁的基本使用
#include <pthread.h>
// 阻塞时加锁
int pthread_mutex_lock(pthread_mutex_t *mptr);
// 非阻塞式加锁
int pthread_mutex_trylock(pthread_mutex_t *mptr);
// 释放锁
int pthread_mutex_unlock(pthread_mutex_t *mptr);
// 销毁锁
int pthread_mutex_destroy(pthread_mutex_t *mptr);
// 成功均返回 0
// 非阻塞式加锁在锁被其他线程占用时返回,不阻塞。
// 销毁锁时需确保锁处于 unlock 状态。
(2)example(注意两种初始化互斥锁的方法)
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
// 1 static
//pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 2 dynamic
pthread_mutex_t mutex;
// the data to be protected
int count = 0;
void * print(void *arg)
//pthread_detach(pthread_self());
for (int i = 0; i < 10; i++)
pthread_mutex_lock(&mutex);
count++;
printf("thread %lu -- count is : %d\\n", pthread_self(), count);
pthread_mutex_unlock(&mutex);
return NULL;
int main(void)
// 2 dynamic
pthread_mutex_init(&mutex, NULL);
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, print, NULL);
pthread_create(&tid2, NULL, print, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_mutex_destroy(&mutex);
printf("over\\n");
return 0;
以上是关于C中进程与线程的基本使用(结合代码讲解)的主要内容,如果未能解决你的问题,请参考以下文章
啥是指针?指针怎么用?来看C指针快速入门(结合代码和图例讲解,通俗易懂)