初识Linux
Linux基础命令
sudo apt update
sudo apt install -y ssh
sudo apt install vim
sudo apt install net-tools
sudo apt install manpages manpages-dev
sudo snap install code --classic
Linux中一切皆文件
- 会将各种硬件(CPU/内存)映射成文件
- 将运行程序进程的内存映射成文件
Linux目录结构
/bin 可执行命令程序的目录文件
/home 用户的家目录,可能包含n个用户的文件夹,默认就只有我们创建的登陆用户(后期可以创建)
/etc 系统配置文件目录, 比如:passwd(用户信息)
/usr 用户的安装程序目录, 相当于windows中的Program Files
常用基本命令
帮助命令
-
man 命令名 查看命令文档(当只能查看shell外部命令)
-
help 命令名 查看内部命令文档
-
sudo apt install manpages manpages-dev
下载最新的man文档 -
shell命令的分类
- 内置命令(系统内部提供): help
- 外置命令(bin下): man
-
常用快捷键
Ctrl + C 或 Q: 停止退出
Ctrl + L 或 clear: 清屏
Tab键: 补全
上下键:查找执行过的命令
文件目录类命令
- pwd
- print work directory
- 输出当前目录的绝对路径
-
ls
- list
- 列出目录下的文件列表
- ls [目录路径]
- ll [目录路径]
- ls -R [目录路径]
-
cd
- change dirctory
- 切换目录
- cd 路径
- cd …
- cd
- cd -
-
mkdir
- make directory
- 创建目录
- mkdir 一层目录
- mkdir -p 多层目录
-
touch
- 创建文件
- touch 文件路径
-
cp
- copy
- 复制文件或目录
- cp 源文件路径 目标目录 =》复制文件
- cp -r 源文目录 目标目录 =》 复制目录
-
rm
- remove
- 删除文件或目录
- rm 文件路径
- rm -r 目录
-
mv
- move
- 移动/重命名文件或目录
- mv 源文件路径 目标文件路径 =》 如果同目录就是重命名
-
cat
- catenate
- 查看输出文件内容
- 适合一屏显示的小文件
- cat -n 文件路径
-
tail
- 显示文件最后面内容, 默认10行
- tail -n 数值 文件路径 =》 显示最后指定数量行的内容
- tail -F 文件路径 =》 带监视的显示,一旦有更新会自动更新显示
-
echo
- 输出指定内容
- echo 文本内容 =》有空格和特殊字符尽量加引号
- echo -e “ab \t cd”
-
与>> 输出重定向
-
覆盖写到右侧指定文件
-
追加写到右侧指定文件
- ls >文件路径
- ll >> 文件路径
-
-
ln
- link
- 创建文件或目录的软链接
- ln -s 源文件或目录 软链接路径
- cd -P 软链接路径 =》进入实际物理路径
-
history
- history =》 显示执行过的命令列表
文件权限类命令
理解权限
文件权限是为了限制不同用户的操作级别,更安全
权限:读(r 4)、写(w 2)、执行(x 1)
文件:
可读:查看文件内容 r 4
可写:修改文件内容 w 2
可执行:可运行 x 1
注意:文件是可重命令和删除得看文件夹的权限
目录
可读:可以ls查看目录下的内容
可写:可以对目录内文件创建和删除,重命令目录
可执行:可进入
内部使用3个二进制位来存储权限:rwx
也就是:4:r, 2: w, 1: x
可以相加得到多个权限:3:wx 5: rx 6: rw 7: rwx
用一个3位8进制位来代表一个文件整体权限
-
chmod
u:所有者 g:所有组 o:其他人 a:所有人(u、g、o的总和)
chmod [{ugoa}{±=}{rwx}] 文件或目录
chmod 权限值 [文件或目录]
chmod -R 权限值 目录 -
chown
改变所有者
sudo chown 最终用户 文件或目录
sudo chown -R 最终用户:最终用户组 文件或目录 -
chgrp
chgrp 最终用户组 文件或目录
搜索查找类命令
1)find
find 目录 -name xxx 根据文件名称查找
find 目录 -user xxx 根据用户名查找
find 目录 -szie 200c 根据文件大小查找
2)grep与 “|”
进行输出过滤
ls |grep xxx
cat a.txt |grep xxx
tar包的压缩和解压类命令
压缩/解压多个文件或文件夹
压缩:tar -zcvf xxx.tar.gz a.txt b.txt test
解压:tar -zxvf xxx.tar.gz
tar -zxvf xxx.tar.gz -C ./bb
进程线程类命令
ps -aux: 查看系统中所有进程
ps -ef: 主要查看父子进程的关系
kill -9 进程号 杀死某个进程
free 查看内存使用情况
top查看系统健康状况
路径类
basename: 得到路径中的除最右侧一层的名称
dirname:得到路径中的除最右侧一层外的目录
shell命令
-
shell是一个命令行解释器它接收应用程序/用户命令,然后调用操作系统内核。
-
Ubuntu默认的解析器是bash
-
脚本格式:脚本以#!/bin/bash开头(指定解析器)。
变量
-
获取变量值
- 语法:$变量名
-
显示当前Shell中所有变量:set
自定义变量
(1)定义变量:变量名=变量值,注意,=号前后不能有空格。
(2)撤销变量:unset 变量名。
(3)声明静态变量:readonly变量,注意:不能重新赋值,不能unset。
- 语法:export 变量名
可把变量提升为全局环境变量,可供其他Shell程序使用
特殊变量
- $n
$n (功能描述:n为数字,$0代表该脚本名称,$1- 9 代表第一到第九个参数,十以上的参数需要用大括号包含,如 9代表第一到第九个参数,十以上的参数需要用大括号包含,如 9代表第一到第九个参数,十以上的参数需要用大括号包含,如{10}。)
- $#
(功能描述:获取所有输入参数个数,常用于循环,判断参数的个数是否正确以及加强脚本的健壮性。)
-
$* $@
-
∗ (功能描述:这个变量代表命令行中所有的参数, * (功能描述:这个变量代表命令行中所有的参数, ∗(功能描述:这个变量代表命令行中所有的参数,*把所有的参数看成一个整体。)
-
@ (功能描述:这个变量也代表命令行中所有的参数,不过 @ (功能描述:这个变量也代表命令行中所有的参数,不过 @(功能描述:这个变量也代表命令行中所有的参数,不过@把每个参数区分对待。)
-
-
$?
$? (功能描述:最后一次执行的命令的返回状态。如果这个变量的值0,证明上一个命令正确执行;如果这个变量的值为非0(具体是哪个数,由命令自己来决定),则证明上一个命令执行不正确了。)
算数运算
- 基本语法 “ ( ( 运算式 ) ) " 或 " ((运算式))" 或 " ((运算式))"或"[运算式]”
条件判断
写法1:test condition
写法2:[ condition ](注意condition前后要有空格)
- 注意:条件成立(数据非空)即为0(真),否则为1(假)
常用判断条件
- 两个整数之间比较
-eq 等于(equal) -ne 不等于(not equal)
-lt 小于(less than) -le 小于等于(less equal)
-gt 大于(greater than) -ge 大于等于(greater equal)
- 按照文件权限进行判断
-r 有读的权限(read)
-w 有写的权限(write)
-x 有执行的权限(execute)
- 按照文件类型进行判断
-e 文件存在(existence)
-f 文件存在并且是一个常规的文件(file)
-d 文件存在并且是一个目录(directory)
- 多条件判断(&& 表示前一条命令执行成功时,才执行后一条命令,|| 表示上一条命令执行失败后,才执行下一条命令)
流程控制
if
- 单分支
if [ 条件判断式 ];then
程序
fi
/*或者*/
if [ 条件判断式 ]
then
程序
fi
- 多分支
if [ 条件判断式 ]
then
程序
elif [ 条件判断式 ]
then
程序
else
程序
fi
注意事项:
① [ 条件判断式 ],中括号和条件判断式之间必须有空格
② if后要有空格
case
case $变量名 in
"值1")
如果变量的值等于值1,则执行程序1
;;
值2)
如果变量的值等于值2,则执行程序2
;;
…省略其他分支…
*)
如果变量的值都不是以上的值,则执行此程序
;;
esac
注意事项:
(1)case行尾必须为单词“in”,每一个模式匹配必须以右括号“)”结束。
(2)双分号“;;”表示命令序列结束,相当于C中的break。
(3)最后的“*)”表示默认模式,相当于C中的default。
for
for ((初始值;循环控制条件;变量变化))
do
程序
done
/*或者*/
for 变量 in 值1 值2 值3…
do
程序
done
while
while [ 条件判断式 ]
do
程序
done
read命令
- 基本语法 : read (选项) (参数)
选项:
-p:指定读取值时的提示符。
-t:指定读取值时等待的时间(秒)如果-t不加表示一直等待。
read -p “输出的提示文本” -t 秒数 变量名
后面就可以通过$变量名得到输入的内容
函数
- 基本语法
[ function ] funname[()] 注意只能省略其中一个
{
Action;
[return int;]
}
函数返回值,只能通过$?系统变量获得,可以显示加:return返回,如果不加,将以最后一条命令运行结果,作为返回值。return后跟数值n(0-255)。
Shell工具
cut
- 基本用法 : cut [选项参数] filename
选项参数 功能
-f 列号,提取第几列
-d 分隔符,按照指定分隔符分割列,默认是制表符“\t”(只能是一个字符)
例如:从文本文件中剪出目标文本
cut -d " " -f 1 cut.txt
它没有过滤行的功能,很多时候需要借助grep进行过滤操作
awk
- 基本用法 : awk [选项参数] ‘/pattern1/{action1} /pattern2/{action2}…’ filename
pattern:表示awk在数据中查找的内容,就是匹配模式。
action:在找到匹配内容时所执行的一系列命令。
选项参数 功能
-F 指定输入文件的分隔符
-v 赋值一个用户定义变量
- awk内置变量:
变量 说明
FILENAME 文件名
NR 行号
NF 切割后列的个数
正则表达式
^ 匹配一行的开头
$ 匹配一行的结束
. 匹配一个任意的字符
* 不单独使用,他和上一个字符连用,表示匹配上一个字符0次或多次
[ ] 表示匹配某个范围内的一个字符
\ 表示转义
版本控制
Git安装
windows:运行git.exe,按图示进行安装
linux:sudo apt install git
配置用户名和邮箱:
git config --global user.name [name]
git config --global user.email [email]
git的基本命令
git三个重要的区:工作区 、 暂存区、版本区(本地仓库)
添加git忽略配置:.gitignore
git init
初始化仓库,生成工作区 => 生成 .git文件夹
git add .
将工作区的更新添加到暂存区
git commit -m "xxx"
将暂存区的更新提交到本地仓库
git status
查看工作区和暂存区的状态(是否有更新)
git reflog
查看历史版本信息
git reset --hard
版本号 得到指定版本的代码
远程仓库
- 配置用户名和邮箱
git config --global [用户名]
git config --global user.email [邮箱]
- 关联远程仓库
git remote add origin [仓库地址]
- 将本地仓库的main分支推送到远程仓库的master分支
git push -u origin main:master
会提示输入用户名和密码
- 克隆仓库
git clone [仓库地址]
- 解决冲突(远程)
执行git推送(push)时,如果远程对应的分支已更新,会提示必须先拉取(pull)更新
执行git拉取时,如果远程和本地仓库都改了同一个文件就会出冲突
默认不会进行带冲突的合并,需要添加配置指定自动合并:git config pull.rebase false
再次执行git拉取,会自动进行带冲突合并
打开文件,修正好冲突合并的文件
添加到暂存区 => 提交到本地仓库 => 推送到远程
Linux应用
GCC、glibc与GNU C的关系
- GNU C则定义了GCC支持的C语言的标准和扩展
- glibc包含了C语言的多个运行库,比如:stdio、stdlib、unistd、fcntl、string等,提供程序运行所需的标准函数和操作系统服务的接口
- GCC是编译器,负责将源代码转换为可执行的机器代码
使用VSCode
- 下载安装:
sudo snap install code --classic
- 安装gcc:
sudo apt install gcc
C程序的编译过程
- 包含四个重要处理:C源文件 --> 预处理 => 编译 => 汇编 => 链接 --> 完整的可执行程序
1)预处理
选项参数:-E
生成:xxx.i
作用:将省略和引入的内容展开添加进来,删除代码注释
编译
选项参数: -S
生成:xxx.s
作用:生成汇编代码,还包括编译器和操作系统版本信息汇编
选项参数:-c
生成:xxx.o
作用:对单个汇编源代码编译生成机器码文件
机器码文件内部组成
查看:objdump -s xxx.o
主要的节(section): 组成单元
.text节: 程序的机器代码或指令,是程序实际执行的代码
.comment节:包含编译器版本的信息链接
选项参数:无
生成:xxx
作用:链接依赖的库函数,生成一个总的可执行文件
区别静态链接与动态链接
静态链接:直接将依赖的库代码添加到机器码中(文件比较大,占用空间大,但可以独立运行,不依赖外部库)
动态链接:只将依赖库的位置添加到机器码中(文件比较小,占用空间小,但依赖于外部的库,默认是动态链接)
Make
Make是一个自动化构建工具,用于从源代码到构建程序、运行和删除文件等操作 .我们需要在Makefile文件中定义不同的目标来执行不同的操作.
- 下载包含Make的工具包:
sudo apt install -y build-essential
- 编写Makefile文件
定义目标
目标名称:前置依赖(目标/文件)
命令
说明
目标一般都指定为要生成的同名文件
在执行某个目标的命令前,会先执行前置依赖目标,生成依赖文件
目标文件: 依赖文件
-gcc $^ -o $@
-./$@
-rm $@
$^:依赖文件
$@:目标文件
-
定义变量
objects := hello.o main.o objects := hello.o \ main.o object += xxx.o $(objects)
-
定义伪目标
为什么要定义? —防止有同名文件后,目标总是不能再执行(对应的文件已有了)
哪些目标要定义?—不生成对应文件的目标
.PHONY= target1,target2
-
问题:为什么要指定依赖文件?
理解:如果不指定依赖文件,如果目标文件已存在,但依赖文件发生了改变,再次执行目标,不重新命令生成新的
当然:如果目标文件和依赖文件都没有发生变化,重新执行目标肯定不再执行命令了
文件IO
-
区别库函数与系统调用?
系统调用是操作系统内核提供给应用程序特定功能函数,使其可以间接访问硬件资源。
而库函数是系统提供的标准库或第三方提供的库所包含的特定功能函数,库函数内部可能会使用到系统调用,当然也可能完全没有使用系统调用。 -
IO库函数
fopen(): 打开一个文件流 fclose():关闭文件流 fputc():向文件中写入一个字符 fputs():向文件中写入一个字符串 fprintf():向文件中写入指定格式的字符串 fgetc():读取文件中的一个字符 fgets():读取文件中的一个字符串 fscanf(): 读取文件中的一个特定格式的字符串,并保存到多个变量 stdin:读取终端的标准输入流 stdout:写到终端的标准输出流 stderr:写到终端的标准错误输出流 输出重定向:可以将stdout和stderr的数据重定向到不同的日志文件中
errno:错误编号, 后面可以根据strerrono得到错误信息
perror():输出错误信息
```
-
IO系统调用
- open():打开文件流,得到对应当前文件的文件描述符标识 fd
参数1:文件路径
参数2:文件模式(只读、只写、追加写、创建)
参数3:文件的权限值(创建模式) 0654
返回:文件描述符标识 - close():关闭指定文件描述符的文件流
参数:文件描述符标识 - read():读取文件中的数据
参数1:文件描述符标识
参数2:缓存读取的数据的字符数组
参数3:最大读取字节数
返回:真实读取的字节数 - write():向文件中写入数据
参数1:文件描述符标识
参数2:存储了数据的字符数组
参数3:数据的字节数
- open():打开文件流,得到对应当前文件的文件描述符标识 fd
-
退出程序的函数
-
exit(status)
:
库函数,在stdlib中定义
会自动关闭文件或刷新输出
结束速度稍慢
适合父进程中使用,自动清理释放 =》但开发中为了确定成无一失,很多人都会手动清理释放 -
_exit(status)
:
系统调用, 在unistd中定义
不自动执行任何清理操作(刷新IO缓冲区,关闭IO流)
结束速度稍快
子进程处理失败,如果不想自动IO刷新/关闭的操作而影响到父进程,使用exit
-
进程处理
进程(Process)是正在运行的程序,是操作系统进行资源分配和调度的基本单位。程序是存储在硬盘或内存的一段二进制序列,是静态的,而进程是动态的。进程包括内存中的代码、数据以及分配给它的其他系统资源(如文件描述符、网络连接等)。
- 理解进程内存模型(结构) 地址由小及大
文本(程序代码: 程序代码、指令
初始化数据:已经初始化的全局变量和静态变量
未初始化数据:没有初始化的全局变量和静态变量
堆(heap):malloc动态分配
未分配内存:供堆和栈扩展使用的区域
栈:局部变量、函数参数、返回地址等
参数与环境: 程序参数和环境变量内核:内核空间,系统内部使用,无法直接访问,需要通过系统调用访问
进程处理的相关系统调用
fork()
:根据当前进程,创建一个新的子进程
返回值:pid_t 本质是int类型:
失败:-1
进入子(新的)进程执行: 0
进入父(当前)进程执行: 子进程的pid
-
注意:
创建子进程成功后,fork函数会返回2次,一次是在主进程中执行,一次是在子进程中执行
先执行哪个进程不是固定的,由系统内核调度决定
子进程不会再执行fork之前的代码
子进程会拷贝一份父进程的内存,所以fork之前声明的变量在子进程也有一份 -
execve()
/* 作用:在当前进程执行指定的程序 参数1:可执行文件的路径(自定义的或系统的) 参数2:包含执行文件路径、要传递的参数的字符串数组 (最后一个元素必须是NULL) 参数3:包含PATH环境变量的字符串数组(最后一个元素必须是NULL,可以直接为{NULL}表示用默认的) 返回值 失败:-1 成功:函数不返回,后面的代码不会执行 * int execve (char *__path, char *__argv[], char *__envp[]) */
-
waitpid(pid)
、wait(null)
/** 作用:等待指定子进程执行完后再接着执行当前进程 参数1:子进程id 参数2:接收子进程执行返回int值结果的指针,如果子进程正常执行完,值将为0,否则是其它值 参数3:用来指定wait特点的特定数值 WNOHANG(1): 当前进程不阻塞,不会等待目标进程执行完当前进程就继续向下执行,得不目标进程返回的结果 WUNTRACED(2):会等待目标进程执行完当前进程才向下执行,且能得到子进程的返回结果 0: 效果赞同于WUNTRACED, 一般直接写0 * 返回值: * WNOHANG时: 0 * WUNTRACED:子进程的pid * 如果目标进程不存在: -1 * * __pid_t waitpid (__pid_t __pid, int *__stat_loc, int __options) */
进程间通信的多种方式
- 管道
-
利用管道实现两个进程间的单向通信
-
编码流程
/** * 作用:创建管道 * 参数:用来接收管道读和写2个文件描述符的数组,[0]为读,[1]为写 * 返回:成功:0, 失败:-1 * * int pipe (int pipe_fds[2]) */ int pipe(int pipefd[2]); #define EXIT_FAILURE 1 /* 失败退出状态 */ #define EXIT_SUCCESS 0 /* 成功退出的状态 */ #define STDOUT_FILENO 1 /* 标准输出流文件描述符 */
通过
fork
产生子进程时,产生管道读写描述符的拷贝
如果是进程A向进程B发送消息数据
进程A关闭管道的读,通过写描述符写入数据
进程B关闭管道的写,通过读描述符读取A进程写入到管道中的数据 -
注意:
一个管道只能进行A向B或者B向A的单向通信 (半双工)
如果需要双向,得创建2个管道
得利用fork
来产生同一个管道的多个管道读写描述符
2)共享内存
-
利用两个进程操作同一个内存共享对象对应一块共享物理内存进行数据读和写操作来实现进程间通信
-
编码流程
shm_open()
:创建内存共享对象,共享文件默认在**/dev/shm/**文件夹下
truncate()
:将共享对象内存扩展到特定大小
mmap()
:将共享对象映射到虚拟共享内存(当前不开辟物理内存),得到共享内存的地址munmap()
:释放共享内存
shm_unlink ()
:删除共享内存对象
fork进程:父子进程共享一个共享对象、两块共享虚拟内存(但对应的是同一个物理内存)
在不同进程中,可以通过共享内存的地址写数据和读数据,从而实现两个进程间通信
释放共享内存,删除共享对象
- 注意
共享对象本质是linux的tempfs临时文件系统文件,数据会保存到其中,但它是不落盘的(只是在内存中)
不限制方向,只要保证一个进程写,另一个进程读即可
轻松就可以实现两个进程相互通信
3)消息队列
-
利用Linux内核维护的消息队列,一个进程向队列发数据,另一个进程从队列读取出数据来实现进程间通信
编码流程:struct mq_attr attr;
定义配置队列的structmq_open()
:创建/打开消息队列, 得到队列的描述符,在**/dev/mqueue/**下生成对应文件(消息保存在内存队列中,并不保存在此文件中)
fork()
创建子进程
mq_send()
:主进程向队列发消息
mq_receive()
:子进程从队列中读取出最前面的消息
close()
:关闭队列流
mq_unlink()
:删除队列
注意:
支持任意多个进程间的相互通信,有进程发数据给队列,有进程从队列中取数据
4)信号
- 信号是一种用于通知进程发生了某种事件的机制.可以由内核、其他进程或者通过命令行工具发送给目标进程
- 常见的信号包括:
SIGINT(2):这是当用户在终端按下Ctrl+C时发送给前台进程的信号,通常用于请求进程终止。
SIGKILL(9):这是一种强制终止进程的信号,它会立即终止目标进程,且不能被捕获或忽略。
SIGTERM(15):这是一种用于请求进程终止(kill 进程)的信号,通常由系统管理员或其他进程发送给目标进程。
signal
:注册特定信号的监听, 绑定回调函数
exit(EXIT_SUCCESS)
:成功退出
线程处理
- 每个线程都有自己的栈空间
- 共享地址空间和数据段
- 多线程比多进程更适合高并发
区别进程与线程
- 进程是一个运行的程序,有独立私有的内存空间和资源
- 线程是进程内的一个执行单元,一个进程内的所有线程共享进程的空间和资源
- 线程间共享更简单,一个全局变量就搞定,但带来的线程同步问题
- 线程间切换相比进程间切换消耗更小,更适合做高并发处理。
线程控制
1) 创建线程
pthread_create()
:创建一个线程
/**
作用:创建一个线程
参数1:线程标识符的指针,线程创建成功时,用于存储新创建线程的线程标识符
参数2:pthead_attr_t结构体,这个参数可以用来设置线程的属性,如优先级、栈大小等。
如果不需要定制线程属性,可以传入 NULL,此时线程将采用默认属性。
参数3:新线程函数的指针
参数4:线程函数的参数,可以是一个指向任意类型数据的指针
返回值:成功:0,失败非0
*/
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine)(void *), void *arg);
每个线程都有一个唯一的标识符(注意不是线程id),这个标识符是通过pthread_t类型的变量来表示的
gettid
:
#define _GNU_SOURCE // 如是不声明gettid()不识别报错
#include <unistd.h>
#include <sys/types.h>
/*
#define _GNU_SOURCE
请求编译器启用GNU libc(GNU C库)中所有的扩展特性
gettid函数并不是标准C库或POSIX线程库(pthreads)直接提供的函数,而是Linux内核中的一个系统调用。
由于它没有在像glibc这样的标准C库中声明,所以在用户空间代码中直接使用gettid时,编译器会报未定义的错误。
*/
/**
* @brief 返回调用者的线程ID
*
* @return pid_t 成功返回线程ID,该调用不会失败。
*/
pid_t gettid(void);
2) 终止线程
- 方式一:线程函数执行return语句
- 方式二:线程函数内部调用
pthread_exit
函数 - 方式三:其他线程调用
pthread_cancel
函数 - 一旦main线程结束,进程也结束,如果还有线程未执行完就不可能再执行了
- 一个线程等待另一个线程结束:
pthread_join()
,经常用于main线程等待其它线程结束
#include <pthread.h>
/**
作用:等待指定线程结束,获取目标线程的返回值,并在目标线程结束后回收它的资源
参数1: 线程的标识
参数2:可选参数,用于接收线程结束时传递的返回值的指针的指针
线程函数的返回值可以通过return 或 pthread_exit()指定,如果都没有则为NULL或忽略
返回值:成功 0, 失败 1
*/
int pthread_join(pthread_t thread, void **retval);
线程同步
1)概念
- 多线程并发执行:多个线程同时执行,且很可能操作同一份数据,且一个线程执行过程中可能被系统暂停切换到另一个线程执行
- 线程同步:多个线程共享操作同一份数据,不要出现一个线程对数据进行读写的中间被切换到另一个线程执行的情况,另一种就是控制线程顺序执行。
- 竞态条件:是一种特定的线程同步问题,指的多个进程或者线程并发执行时,其最终的结果依赖于进程或者线程执行的精确时序,它可能会导致执行结果不对。
- 避免竞态条件的最常见办法:给资源加锁,某一时间段内只能有一个线程操作数据 =》互斥锁
- 常见的线程间同步技术:互斥锁 、条件变量、信号量
2)互斥锁
- 互斥锁是一种同步机制,用来控制对共享资源的访问。在任何时刻,最多只能有一个线程持有特定的互斥锁。如果一个线程试图获取一个已经被其他线程持有的锁,那么请求锁的线程将被阻塞,直到锁被释放。
- 互斥锁的操作
PTHREAD_MUTEX_INITIALIZER
:初始化
pthread_mutex_lock()
:获取锁,如果此时锁被占则阻塞,否则函数返回,向下执行
pthread_mutex_unlock()
:释放锁,获取锁而阻塞的线程就有机会获取到锁而向下执行
pthread_mutex_destroy()
:销毁锁,释放锁占用的所有内存和资源
3)条件变量
- 用于线程间的条件切换:
当达到某个条件时,当前线程暂停让另一个线程执行,当达到另一个条件时,另一个线程也会暂停让暂停的线程恢复执行 - 相关操作:
PTHREAD_COND_INITIALIZER
:初始化条件变量
pthread_cond_wait()
:暂停线程等待唤醒
pthread_cond_signal()
:唤醒等待的线程 - 注意:只有使用上互斥锁,才能用上条件变量
4)信号量
-
信号量本质是用来实现进程间同步的技术(当然也可以用于线程间同步),用于控制对共享资源的访问
-
分类
二进制信号量(0/1), 用于互斥,功能相当于互斥锁
计数信号量(任意非负整数),用来控制线程、进程的执行顺序。 -
相关操作
sem_init()
:初始化信号量, 指定信号量的初始值为0sem_wait()
:等待信号量(也称:执行P操作)
如果信号量>0, 信号量减1,函数返回向下执行
如果信号量=0, 阻塞等待直至信号量>0
sem_post()
:信号操作,将信号量值加1,因为信号量阻塞的线程就会被唤醒
避免竞态条件的最常见办法:给资源加锁,某一时间段内只能有一个线程操作数据 =》互斥锁
原文地址:https://blog.csdn.net/m0_74967868/article/details/144408525
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!