一、多进程

引入目的:让多个任务实现并发执行

1.1 进程概念

1、程序的一次运行过程称为进程

2、进程是有生命周期的,是一个动态过程,分为创建态、就绪态、运行态、阻塞态、消亡态

3、进程是资源分配的最小单位,系统会给每个进程分配4G的虚拟内存,其中0–3G的用户空间是独立的,3–4G的内核空间时共享的

4、进程是独立的,可以被任务器调度,调度的原则是时间片轮询、上下文切换

img

1.2 进程的内存管理

1> 系统会给每个进程分配4G的虚拟内存

2> 多个进程会独立拥有0–3G的用户空间,用户空间又分为栈区、堆区、静态区

3> 多个进程共享一份内核空间

4> 物理内存:内存条(硬件上)真正存在的存储空间

5> 虚拟内存:程序运行后,有4G的虚拟地址,由物理内存通过内存映射单元映射而来,在需要使用内存的时候,会映射到物理内存上

6> 在32位操作系统上,虚拟内存的空间是4G

​ 在64位系统上,虚拟内存的空间是256T = 2^48

img

1.3 进程和程序的区别

进程:进程是动态的,进程是程序的一次执行过程,有生命周期,进程会为自己分配内存空间,是资源分配的最小单位

程序:程序是静态的,没有所谓的生命周期,它是在磁盘上存放的二进制文件

1.4 进程的种类

进程一共分为三类:交互进程、批处理进程、守护进程

交互进程:他是由shell控制,可以直接与用户进行交互,例如文本编辑器

批处理进程:维护了一个队列,被放入队列中的进程会统一进行处理。例如gcc编译器的一步到位的编译

守护进程:脱离终端而存在的进程,随着系统的启动而运行,随着系统的关闭而结束。例如服务进程

1.5 进程的PID概念

PID:进程号(process ID)

PPID:父进程号

进程号是进程的唯一标识,他是一个大于等于0的一个整数,并且每个进程的进程号不会重复

每个进程都是继承父进程而得到的,所以每个进程都会有父进程

在linux系统中的根目录下的proc目录中,存放的以数字命名的都是一个进程

img

1.6 特殊的进程

1> 系统启动后,至少要运行三个特殊进程,进程号分别是 0、1、2

2> 0号进程:又称为 idel进程,他是有linux系统启动后的第一个进程,是1号和2号进程的父进程,这个进程也叫空闲进程,当系统中的其他进程都不执行时,运行该进程。

3> 1号进程:称为 init进程,是由0进程产生,完成一些系统启动时的必要初始化工作,也是孤儿进程的父进程,可以完成对孤儿进程的收尸工作

4> 2号进程:称为kthreadd进程,是右0号进程产生,用于调度相关进程,也称调度进程

5> 孤儿进程:当前进程还在运行,但是其父进程已经退出,该进程称为孤儿进程,会被init进程收养

6> 僵尸进程:当前进程已经运行结束,但是其父进程没有为其收尸

1.7 有关进程的shell指令

1> 查看进程信息指令:ps

ps -ef : 可以查看进程间的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
UID         PID   PPID  C STIME TTY          TIME CMD
root 1 0 0 一月01 ? 00:00:25 /sbin/init splash
root 2 0 0 一月01 ? 00:00:00 [kthreadd]
root 3 2 0 一月01 ? 00:00:00 [rcu_gp]
root 4 2 0 一月01 ? 00:00:00 [rcu_par_gp]
root 6 2 0 一月01 ? 00:00:00 [kworker/0:0H-kb]
root 8 2 0 一月01 ? 00:00:00 [mm_percpu_wq]
root 9 2 0 一月01 ? 00:00:05 [ksoftirqd/0]
root 10 2 0 一月01 ? 00:01:05 [rcu_sched]
root 11 2 0 一月01 ? 00:00:01 [migration/0]
UID:当前进程的用户id
PID:当前进程的进程号
PPID:当前进程的父进程号
STIME:开始运行的日期
TTY:如果为问号,表明该进程不依赖于任何终端存在
CMD:进程名称

ps -ajx:可以查看进程的状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  PPID    PID   PGID    SID TTY       TPGID STAT   UID   TIME COMMAND
0 1 1 1 ? -1 Ss 0 0:25 /sbin/init splash
0 2 0 0 ? -1 S 0 0:00 [kthreadd]
2 3 0 0 ? -1 I< 0 0:00 [rcu_gp]
2 4 0 0 ? -1 I< 0 0:00 [rcu_par_gp]
2 6 0 0 ? -1 I< 0 0:00 [kworker/0:0H-kb]
2 8 0 0 ? -1 I< 0 0:00 [mm_percpu_wq]
2 9 0 0 ? -1 S 0 0:05 [ksoftirqd/0]
2 10 0 0 ? -1 I 0 1:05 [rcu_sched]
2 11 0 0 ? -1 S 0 0:01 [migration/0]
2 12 0 0 ? -1 S 0 0:00 [idle_inject/0]

PGID:当前进程所属组的id号
SID:当前进程所在会话组的id
TPGID:如果是-1,表示该进程是一个守护进程
STAT:当前进程的状态,一个状态包含主状态栏和附加态

ps -aux:可以查看当前进程所占内存和cpu的资源占有率

1
2
3
4
5
6
7
8
9
10
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root 1 0.0 0.3 225504 6920 ? Ss 一月01 0:25 /sbin/init sp
root 2 0.0 0.0 0 0 ? S 一月01 0:00 [kthreadd]
root 3 0.0 0.0 0 0 ? I< 一月01 0:00 [rcu_gp]
root 4 0.0 0.0 0 0 ? I< 一月01 0:00 [rcu_par_gp]
root 6 0.0 0.0 0 0 ? I< 一月01 0:00 [kworker/0:0H
root 8 0.0 0.0 0 0 ? I< 一月01 0:00 [mm_percpu_wq
root 9 0.0 0.0 0 0 ? S 一月01 0:05 [ksoftirqd/0]
root 10 0.0 0.0 0 0 ? I 一月01 1:05 [rcu_sched]

2> top指令:动态查看进程相关信息

htop指令:带颜色查看进程相关信息

img

3> 单独获取进程的进程号:pidof 进程名

4> 以树状图的形式显示进程间关系:pstree

5> 向进程发送信号指令:kill指令

使用格式:kill -信号号 pid

能够发送的信号,可以通过指令kill -l查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX

特殊的信号:
SIGKILL(9):杀死指定进程信号,该信号,既不能被捕获,也不能被忽略
SIGHUP(1):当进程所在的终端关闭后,终端进程会给当前终端中的每个进程发送该信号,默认操作杀死进程
SIGINT(2):当用户按下ctrl + c后,进程会收到该信号
SIGQUIT(3):退出指定的进程,用户键入ctrl + \时产生
SIGSEGV(11):当指针使用不当或堆栈溢出时,内核空间发射该信号,表示段错误
SIGCHLD(17):当子进程退出后,会向父进程发送该信号,表示我已经死了
SIGCONT(18):让暂停的进程继续运行
SIGSTOP(19)、SIGTSTP(20):让进程暂停执行

二、进程状态

2.1 进程状态的描述

man ps

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
进程的主状态:
D uninterruptible sleep (usually IO) 不可中断的休眠态,通常是进行IO操作
R running or runnable (on run queue) 运行态
S interruptible sleep (waiting for an event to complete) 可中断的休眠态
T stopped by job control signal 停止,由信号控制
t stopped by debugger during the tracing 停止态,调试时的停止态
W paging (not valid since the 2.6.xx kernel) 已经弃用的状态
X dead (should never be seen) 死亡态,不会被看到
Z defunct ("zombie") process, terminated but not reaped by its parent 僵尸态,已经退出,但是父进程没有收尸
附加态:
< high-priority (not nice to other users) 高优先级的进程
N low-priority (nice to other users) 低优先级的进程
L has pages locked into memory (for real-time and custom IO) 锁在内存中的进程,不会进入swap分区
s is a session leader 会话组组长
l is multi-threaded (using CLONE_THREAD, like NPTL pthreads do) 包含多线程的进程
+ is in the foreground process group 前台运行的进程

2.2 进程状态的切换

进程主要的状态一共有五种:创建态、就绪态、运行态、阻塞态、死亡态

img

2.3 具体状态转换的实例

运行一个可执行程序,并查看其状态

查看后台运行进程的作业号:jobs

将停止的进程,切换到后台运行:bg 作业号

将后台运行的进程,切换到前台运行:fg 作业号

直接将程序运行于后台:./a.out &

img

休眠进程的实例

img

三、多进程编程

3.1 创建进程

1> 进程创建过程是子进程拷贝父进程的资源,进而产生一个独立的进程个体,子进程会拥有父进程在创建进程之前的所有资源

2> 创建进程的api函数 fork

1
2
3
4
5
6
7
8
       #include <sys/types.h>
#include <unistd.h>

pid_t fork(void);
功能:创建出一个子进程
参数:无
返回值:在父进程中,该函数返回子进程的pid号,在子进程中该函数返回0,失败返回-1并置位错误码,并且不会创建出子进程
注意:当子进程创建出来后,父子进程都会执行fork之后的语句

3> 不关注返回值的情况时

img

img

不关注返回值情况时,n个fork会产生2^n个进程,并且多个进程之间没有先后顺序执行

4> 关注返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<myhead.h>

int main(int argc, const char *argv[])
{
pid_t pid = -1; //定义变量存储进程号

pid = fork(); //创建一个子进程

//后续的程序,父子进程都会执行
if(pid > 0)
{
printf("这是父进程,pid = %d\n", pid);
}else if(pid == 0)
{
printf("这是子进程,pid = %d\n", pid);
}else
{
perror("fork error");
return -1;
}

printf("hello world\n");
while(1);


return 0;
}

img

5> 验证多个任务能够并发执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include<myhead.h>

int main(int argc, const char *argv[])
{
pid_t pid = -1;

//创建出子进程
pid = fork();

if(pid > 0)
{
//父进程要执行的任务
char sendbuf[128] = "";

while(1)
{
scanf("%s", sendbuf);
printf("您发送的数据%s\n", sendbuf);
if(strcmp(sendbuf, "quit") == 0)
{
break;
}
}


}else if(pid == 0)
{
//子进程要执行的任务
char rcvbuf[20] = "";

//打开文件
int fd = open("./01test.c", O_RDONLY);
if(fd == -1)
{
perror("open error");
return -1;
}

while(1)
{
memset(rcvbuf, 0, sizeof(rcvbuf)); //清空内容
int res = read(fd, rcvbuf, sizeof(rcvbuf));
write(1, rcvbuf, res); //输出到终端
if(res == 0)
{
break;
}

sleep(2);
}


}else
{
perror("fork error");
return -1;
}


while(1);

return 0;
}

6> 验证子进程拷贝父进程在fork之前的资源,fork后,父子进程独立

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<myhead.h>

int main(int argc, const char *argv[])
{
pid_t pid = -1;
int num = 520; //父进程的资源

//创建子进程
pid = fork();
if(pid > 0)
{
//父进程
num = 1314;
printf("父进程中num = %d, %p\n", num, &num); //1314

}else if(pid == 0)
{
//子进程
printf("子进程中num = %d, %p\n", num, &num); //520
}else
{
perror("fork error");
return -1;
}

while(1);

return 0;
}

7> 写时拷贝技术

img

3.2 getpid/getppid 进程号的获取

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <sys/types.h>
#include <unistd.h>

pid_t getpid(void);
功能:获取当前进程的pid号
参数:无
返回值:成功返回当前进程的pid号,不会失败

pid_t getppid(void);
功能:获取当前进程的父进程的pid号
参数:无
返回值:成功返回当前进程的父进程pid号,不会失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<myhead.h>

int main(int argc, const char *argv[])
{
pid_t pid = -1;

//创建子进程
pid = fork();
if(pid > 0)
{
//父进程
printf("父进程中:pid=%d, child pid=%d, parent pid=%d\n",\
getpid(), pid, getppid());

}else if(pid == 0)
{
//子进程
printf("子进程中:pid=%d, parent pid=%d\n", \
getpid(), getppid());
}else
{
perror("fork error");
return -1;
}

while(1);

return 0;
}

3.3 exit/_exit 进程退出函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
       #include <stdlib.h>

void exit(int status);
功能:刷新标准io的缓冲区后,退出进程
参数:进程退出时的状态,会将 status&0377的结果返回给父进程
EXIT_SUCCESS(0):表示成功退出
EXIT_FAILURE(1):表示失败退出
返回值:无


#include <unistd.h>

void _exit(int status);
功能:不刷新标准io的缓冲区,直接退出进程
参数:进程退出时的状态,会将 status&0377的结果返回给父进程
EXIT_SUCCESS(0):表示成功退出
EXIT_FAILURE(1):表示失败退出
返回值:无
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include<myhead.h>

int main(int argc, const char *argv[])
{
pid_t pid = -1;

//创建子进程
pid = fork();
if(pid > 0)
{
//父进程
printf("父进程中:pid=%d, child pid=%d, parent pid=%d\n",\
getpid(), pid, getppid());

}else if(pid == 0)
{
//子进程
printf("子进程中:pid=%d, parent pid=%d\n", \
getpid(), getppid());

sleep(3);

//输出一组数据
printf("1111111111111111111111");
//exit(EXIT_SUCCESS); //刷新标准io的缓冲区后退出进程
_exit(EXIT_SUCCESS); //不刷新标准io的缓冲区后退出进程



}else
{
perror("fork error");
return -1;
}

while(1);

return 0;
}

3.4 wait/waitpid 进程资源的回收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *wstatus);
功能:阻塞等待子进程的结束并回收子进程的资源,如果子进程不退出,则父进程会一直在该函数处阻塞
参数:接受子进程退出时的状态,一般填NULL,表示不接收
返回值:成功返回退出的子进程的pid号,失败返回-1并置位错误码


pid_t waitpid(pid_t pid, int *wstatus, int options);
功能:可以阻塞也可以非阻塞形式回收子进程的资源
参数1:进程号
>0:表示回收特定的子进程的资源(常用)
=0:回收当前进程所在进程组中的任意一个子进程
=-1:表示回收任意一个子进程 (常用)
<-1:表示回收其他组(组id为pid的绝对值)中的任意一个子进程
参数2:接收子进程退出时的状态,一般填NULL,表示不接收
参数3:是否阻塞的选项
0:表示阻塞
WNOHANG:表示非阻塞回收
返回值:>0:表示成功回收一个子进程,返回该子进程的pid
=0:非阻塞回收资源时,没有子进程退出,该函数返回0
=-1:失败返回-1并置位错误码

1> wait案例实现

img

2> waitpid未回收子进程

img

3> 使用waitpid回收僵尸进程

img

3.5 创建三个进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include<myhead.h>

int main(int argc, const char *argv[])
{
pid_t pid1 = -1;
//创建出一个子进程
pid1 = fork();
if(pid1 == 0)
{
printf("我是老大\n");
sleep(2);
exit(EXIT_SUCCESS);
}else if(pid1 > 0)
{
//创建一个子进程
pid_t pid2 = fork();
if(pid2 == 0)
{
printf("我是老二\n");
sleep(5);
exit(EXIT_SUCCESS);
}else if(pid2 > 0)
{
printf("我是父进程,我来回收资源\n");
wait(NULL);
wait(NULL);

printf("孩子都死了,我也不活了\n");
}else
{
perror("fork error");
return -1;
}
}else
{
perror("fork error");
return -1;
}

return 0;
}

作业

1> 创建出三个进程完成两个文件之间拷贝工作,子进程1拷贝前一半内容,子进程2拷贝后一半内容,父进程回收子进程的资源

3.6 僵尸进程和孤儿进程的验证

1> 僵尸进程

img

2> 孤儿进程

img

3.7 守护进程

1> 守护进程相当于一个服务,不依赖于终端而存在

2> 守护进程随着系统的启动而启动,关闭而关闭

3> 守护进程创建流程

1、创建一个孤儿进程

2、重设守护进程的会话id和组id

3、修改守护进程的操作目录为根目录

4、修改守护进程的创建文件的掩码为最大权限

5、将标准输入、标准输出、标准错误文件描述符重定向到一个文件中

6、该文件称为该守护进程的日志文件

4> 相关API函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
       #include <sys/types.h>
#include <unistd.h>

pid_t setsid(void);
功能:为当前进程创建一个新的会话,并将调用进程设置成会话组组长
参数:无
返回值:新会话的ID,失败返回-1并置位错误码


#include <unistd.h>

int chdir(const char *path);
功能:更改当前进程的操作目录
参数:要操作的起始目录
返回值:成功返回0,失败返回-1并置位错误码

#include <sys/types.h>
#include <sys/stat.h>

mode_t umask(mode_t mask);
功能:设置当前进程创建文件的掩码的值
参数:要创建的掩码值
返回值:成功之前的掩码的值,不会失败
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include<myhead.h>

int main(int argc, const char *argv[])
{
//定义进程号
pid_t pid = fork();

if(pid > 0)
{
//父进程
exit(EXIT_SUCCESS);


}else if(pid == 0)
{
//子进程
//将该孤儿进程,创建一个新的会话
setsid();

//更改守护进程的操作目录
chdir("/");

//给守护进程创建文件的权限为最大权限
umask(0);

//将该进程的标准输入、标准输出、标准错误重定向到文件中
int fd = -1;
if((fd = open("log.txt", O_RDWR|O_CREAT|O_APPEND))==-1)
{
perror("open error");
return -1;
}

dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);

int line = 0;


//将守护进程中的数据,存放到日志文件中
while(1)
{
printf("%d:hello world\n", line++);
fflush(stdout);
sleep(2);
}




while(1);

}else
{
perror("fork error");
return -1;
}

return 0;
}

四、多线程

4.1 多线程概念

1> 多线程也能实现多任务并发执行

2> 多线程:成为轻量版进程(LWP),是粒度更小的调度单元

3> 线程是执行任务的最小单位,进程是资源分配的最小单位

4> 一个进程,可以包含多个线程,但是至少要包含一个线程(主线程)

5> 线程几乎不占用资源,仅仅占用有关线程属性的一些资源,大概有8K左右

6> 同一个进程中的多个线程,共享进程的资源,可能会产生资源的抢占(竞态)

7> 由于线程体较小,任务调度线程时所需开销也较小,所以多任务编程是,大多选择多线程

8> 每个线程拥有自己唯一的一个线程号(tid)

9> 线程的调度原则:时间片轮询上下文切换

10> 关于线程相关函数,需要连接外部库进行操作,编译时需要加上:-pthread

4.2 线程创建 pthread_create

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); // int (int *ptr)(int,int)
功能:在当前进程中,创建一个新的线程
参数1:线程号指针,传递一个存放线程号的变量
参数2:线程属性,一般为NULL,表示使用默认的线程属性
参数3:线程体函数,是一个函数指针,需要传递一个返回值为void*类型参数为void*类型的函数名
参数4:参数3的参数,表示创建线程向新线程中传递(返回)的内容
返回值:成功返回0,失败返回一个错误码(非内核提供的错误码)


Compile and link with -pthread. 编译时需要加上-pthread

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<myhead.h>

//定义分支线程函数体
void *task(void *arg)
{
while(1)
{
printf("我是分支线程\n");
sleep(1);
}


}



/**************************主程序*****************/
int main(int argc, const char *argv[])
{
//定义一个线程号变量
pthread_t tid = 0;

//创建出一个分支线程
if(pthread_create(&tid, NULL, task, NULL) != 0)
{
printf("tid create error\n");
return -1;
}

printf("tid create success tid = %#lx\n", tid);


while(1)
{
printf("我是主线程\n");
sleep(1);
}

return 0;
}

4.3 多线程的资源分配问题

· 同一进程中的多个线程共享进程的资源

可以使用全局变量,完成不同线程间的消息通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<myhead.h>

int sum = 520; //定义一个全局变量


//定义分支线程函数体
void *task(void *arg)
{

printf("我是分支线程\n");

printf("分支线程中sum = %d\n", sum);

}



/**************************主程序*****************/
int main(int argc, const char *argv[])
{
//定义一个线程号变量
pthread_t tid = 0;


//创建出一个分支线程
if(pthread_create(&tid, NULL, task, NULL) != 0)
{
printf("tid create error\n");
return -1;
}

printf("tid create success tid = %#lx\n", tid);
sum = 1314;
printf("主线程中sum = %d\n", sum); //1314

while(1);


return 0;
}

4.4 主线程向分支线程传递数据

传递单个数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include<myhead.h>

int sum = 520; //定义一个全局变量


//定义分支线程函数体
void *task(void *arg)
{
//arg --->void *类型的变量,不能直接解引用,因为不确定解析的字节个数
//(int *)arg -->将void×类型的指针,强制转换为int*类型的指针
//*((int *)arg) --> 指针中的前4字节解析出来的内容 520
//*((char *)arg) -->指针中的前1字节解析出来的内容 57

printf("*((int *)arg) = %d\n", *((int *)arg) ); //输出外部传来的数据
//printf("*((char *)arg) = %d\n", *( (char *)arg) );

printf("我是分支线程\n");


printf("分支线程中sum = %d\n", sum);
*((int *)arg) = 666666; //将传进来的数据更改


}



/**************************主程序*****************/
int main(int argc, const char *argv[])
{
//定义一个线程号变量
pthread_t tid = 0;

int key = 12345; //主线程中的局部变量


//创建出一个分支线程
if(pthread_create(&tid, NULL, task, &key) != 0)
{
printf("tid create error\n");
return -1;
}

printf("tid create success tid = %#lx\n", tid);
sum = 1314;
printf("主线程中sum = %d\n", sum); //1314

sleep(1);
printf("主线程中key = %d\n", key); //?
while(1);


return 0;
}

传递多个数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include<myhead.h>

int sum = 520; //定义一个全局变量

struct Info
{
int key;
char ch;
double PI;

double count;
};


//定义分支线程函数体
void *task(void *arg)
{
//arg --->void *类型的变量,不能直接解引用,因为不确定解析的字节个数
//(int *)arg -->将void×类型的指针,强制转换为int*类型的指针
//*((int *)arg) --> 指针中的前4字节解析出来的内容 520
//*((char *)arg) -->指针中的前1字节解析出来的内容 57

//printf("*((int *)arg) = %d\n", *((int *)arg) ); //输出外部传来的数据
//printf("*((char *)arg) = %d\n", *( (char *)arg) );

printf("我是分支线程\n");


printf("分支线程中sum = %d\n", sum);
//*((int *)arg) = 666666; //将传进来的数据更改

printf("%d, %d, %.2lf\n", \
(*((struct Info*)arg)).key, ((struct Info*)arg)->ch, \
((struct Info*)arg)->PI);


((struct Info*)arg)->count = ((struct Info*)arg)->ch + ((struct Info*)arg)->key + ((struct Info*)arg)->PI;

}



/**************************主程序*****************/
int main(int argc, const char *argv[])
{
//定义一个线程号变量
pthread_t tid = 0;

int key = 12345; //主线程中的局部变量
char ch = '\0';
double PI = 3.14;

//定义一个结构体变量
struct Info buf = {key, ch, PI};

//创建出一个分支线程
if(pthread_create(&tid, NULL, task, &buf) != 0)
{
printf("tid create error\n");
return -1;
}

printf("tid create success tid = %#lx\n", tid);
sum = 1314;
printf("主线程中sum = %d\n", sum); //1314

sleep(1);
printf("主线程中count = %.2lf\n", buf.count); //?
while(1);


return 0;
}

4.5 线程号的获取:pthread_self

1
2
3
4
5
6
7
8
9
10
#include <pthread.h>

pthread_t pthread_self(void);
功能:获取当前线程的线程号
参数:无
返回值:当前线程的线程号,不会失败


Compile and link with -pthread. //编译时需要加上 -pthread

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<myhead.h>

//定义分支线程体函数
void *task(void *arg)
{
printf("我是分支线程:tid = %#lx\n", pthread_self());
}





int main(int argc, const char *argv[])
{
//定义线程号
pthread_t tid;
//创建分支线程
if(pthread_create(&tid, NULL, task, NULL) != 0)
{
printf("tid create error\n");
return -1;
}

printf("我是主线程:分支线程tid = %#lx, 自身tid = %#lx\n", tid, pthread_self());


while(1);

return 0;
}

4.6 线程退出函数:pthread_exit

1
2
3
4
5
6
       #include <pthread.h>

void pthread_exit(void *retval);
功能:退出当前线程
参数:线程退出时的状态,一般填NULL
返回值:无

4.7 线程的回收函数:pthread_join/pthread_detach

1
2
3
4
5
6
7
8
9
10
11
12
13
       #include <pthread.h>

int pthread_join(pthread_t thread, void **retval);
功能:以阻塞的形式回收线程的资源
参数1:要回收的线程tid号
参数2:接收线程退出时的状态,一般填NULL
返回值:成功返回0,失败返回一个错误码

#include <pthread.h>

int pthread_detach(pthread_t thread);
功能:将线程设置成分离态,被设置成分离态的线程退出后,其资源由系统回收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include<myhead.h>

//定义分支线程体函数
void *task(void *arg)
{
//获取当前线程线程号后输出
printf("我是分支线程:tid = %#lx\n", pthread_self());

sleep(5);

//exit(EXIT_SUCCESS); //退出整个进程
pthread_exit(NULL);

}


int main(int argc, const char *argv[])
{
//定义线程号
pthread_t tid;
//创建分支线程
if(pthread_create(&tid, NULL, task, NULL) != 0)
{
printf("tid create error\n");
return -1;
}

//分别输出分支线程线程号和当前线程线程号
printf("我是主线程:分支线程tid = %#lx, 自身tid = %#lx\n", tid, pthread_self());


//阻塞回收线程的资源
//pthread_join(tid, NULL);
//
//printf("成功回收了分支线程的资源\n");

//将线程设置成分离态.线程被设置成分离态后,退出时由系统回收其资源
pthread_detach(tid);
printf("线程已经设置成分离态了\n");

sleep(8);
printf("此时分支线程已经退出了\n");


return 0;
}

4.8 向线程发送取消信号 pthread_cancel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
       #include <pthread.h>

int pthread_cancel(pthread_t thread);
功能:向一个指定线程中发送一个取消请求
参数:线程号
返回值:成功返回0,失败返回非0错误码

#include <pthread.h>

int pthread_setcancelstate(int state, int *oldstate);
功能:设置当前线程是否要接受其他线程的取消请求
参数1:状态
PTHREAD_CANCEL_ENABLE:可接受取消请求(默认)
PTHREAD_CANCEL_DISABLE:不可接收取消请求
参数2:接受当前线程旧的状态,如果不想接受。填NULL即可
返回值:成功返回0,失败返回一个错误码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include<myhead.h>

//定义分支线程体函数
void *task(void *arg)
{

//设置不接收取消请求
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);

//获取当前线程线程号后输出
printf("我是分支线程:tid = %#lx\n", pthread_self());

while(1)
{
printf("我真的还想再活五百年\n");
sleep(1);
}

//exit(EXIT_SUCCESS); //退出整个进程
pthread_exit(NULL);

}


int main(int argc, const char *argv[])
{
//定义线程号
pthread_t tid;
//创建分支线程
if(pthread_create(&tid, NULL, task, NULL) != 0)
{
printf("tid create error\n");
return -1;
}

//分别输出分支线程线程号和当前线程线程号
printf("我是主线程:分支线程tid = %#lx, 自身tid = %#lx\n", tid, pthread_self());


//阻塞回收线程的资源
//pthread_join(tid, NULL);
//
//printf("成功回收了分支线程的资源\n");

sleep(2);

//将线程设置成分离态.线程被设置成分离态后,退出时由系统回收其资源
pthread_detach(tid);
printf("线程已经设置成分离态了\n");

//向分支线程发送一个取消请求
pthread_cancel(tid);


sleep(8);
printf("此时分支线程已经退出了\n");


return 0;
}