dr526

dr526 查看完整档案

成都编辑成都信息工程大学  |  计算机科学与技术 编辑  |  填写所在公司/组织 godgood.club/ 编辑
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

dr526 发布了文章 · 2020-11-21

python+opencv实现颜色检测、轮廓检测、颜色追踪

@TOC

准备工作

python配置numpyopenCv

读取图像和视频

  • 图像
  1. cv2.imread(路径)
  2. cv2.imshow(窗口名称,输出对象)
  3. cv2.waitkey(等待时间)
import cv2
img = cv2.imread("./Resources/3-1P316104441.jpg")//当前项目目录下
cv2.imshow("output", img)
cv2.waitKey(0)
  • 视频
  1. cv2.VideoCapture(路径或数字)
  2. set()

(1) set(3,数字)设置显示区域宽
(2) set(4,数字)设置显示区域高
(3) set(10,数字)设置亮度

  1. read()
  2. cv2.imshow
import cv2
cap = cv2.VideoCapture("./Resources/Ninja Track.mp4")
while True:
    #success是bool值,用于判断是否读取成功
    success, img = cap.read()
    cv2.imshow("video", img)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

基础功能

  • 图像
  1. cv2.cvtColor(对象,cv2.COLOR_BGR2GRAY) 转换为灰度图像
  2. cv2.GaussianBlur(对象,(奇数,奇数),0)设置模糊,值越大,模糊程度越大
  3. cv2.Canny(对象,数字,数字)设置Canny边缘检测器,数字越大,显示的越少
  4. cv2.dilate(边缘对象,矩阵,iteration=数字) 图像膨胀,即:增加图像边缘厚度
  5. cv2.erode(对象,矩阵而过,iteration=数字)图像腐蚀,即:减少图像边缘厚度

裁剪图像

  • 改变图像大小
  1. 对象.shape用于显示图片大小
print(img.shape)#先显示高度,再显示宽度
  1. cv2.resize(对象,(宽度,高度))改变图片大小
imgResize = cv2.resize(img, (200,300))#先定义宽度,在定义高度
  • 裁剪图像

对象[数字:数字,数字:数字]先高度后宽度

imgCropped = img[0:100,200:300]#先高度后宽度

绘制图形和文本

  • 创建简单图像
numpy.zeros((512,512)) 建立矩阵,0表示黑色
  • 为图像添加颜色通道
numpy.zeros((数字,数字,3),numpy.unint8)添加颜色三个颜色通道,颜色值为0~255
  • 为图像上色

对象[数字:数字,数字:数字]=rgb值

img[200:300,:]=255,0,0
  • 绘制线条

cv2.line(对象,(起点坐标),(终点坐标),(rgb颜色值),厚度)

cv2.line(img,(0,0),(300,300),(0,255,0),3)
  • 绘制矩形

cv2.rectangle(对象,(起点坐标),(终点坐标),(rgb颜色值),厚度)

cv2.rectangle(img,(0,0),(100,100),(0,0,255),3)
cv2.rectangle(img,(0,0),(100,100),(0,0,255),cv2.FILLED)填充图形
  • 绘制圆

cv2.circle(对象,(圆心坐标),半径,(rgb颜色值),厚度)

cv2.circle(img,(100,100),30,(255,255,0),2)
  • 显示文字

cv2.putText(对象,“文本内容",(起始坐标),字体样式,缩放比例,(rgb颜色值),厚度)

cv2.putText(img,"good",(200,200),cv2.FONT_HERSHEY_COMPLEX,1,(0,150,0),1)

视角转换

  • 设置待转换坐标

numpy.float32([[坐标1],[坐标2],[坐标3],[坐标4]])

pts1 = np.float32([[111,219],[287,188],[154,482],[352,440]])
  • 设置转换后坐标
numpy,float32([[0,0],[width,0],[0,height],[width,height]])
  • 透视图转换
cv2.getPrespectiveTransform(待转换坐标,转换后坐标)
  • 转换后图像
cv2.warpPerspective(对象,透视图转换,(width,height))
img = cv2.imread("Resources/puke_zhipai-003.jpg")
width,height=250,250
pts1 = np.float32([[111,219],[287,188],[154,482],[352,440]])
pts2 = np.float32([[0,0],[width,0],[0,height],[width,height]])
matrix = cv2.getPerspectiveTransform(pts1,pts2)#透视图转换,将世界坐标系变为屏幕坐标系
imgoutput = cv2.warpPerspective(img,matrix,(width,height))
cv2.imshow("card",imgoutput)

cv2.waitKey(0)

图像拼接

  • 无法调整图像大小
  1. 水平拼接

numpy.hstack((对象,对象))

hor = np.hstack((img, img))
  1. 垂直拼接

numpy.vstack((img,img))

ver = np.vstack((img,img))
  • 能够调整图像大小
  1. 编写stackImages函数
def stackImages(scale,imgArray):
    rows = len(imgArray)
    cols = len(imgArray[0])
    rowsAvailable = isinstance(imgArray[0], list)
    width = imgArray[0][0].shape[1]
    height = imgArray[0][0].shape[0]
    if rowsAvailable:
        for x in range ( 0, rows):
            for y in range(0, cols):
                if imgArray[x][y].shape[:2] == imgArray[0][0].shape [:2]:
                    imgArray[x][y] = cv2.resize(imgArray[x][y], (0, 0), None, scale, scale)
                else:
                    imgArray[x][y] = cv2.resize(imgArray[x][y], (imgArray[0][0].shape[1], imgArray[0][0].shape[0]), None, scale, scale)
                if len(imgArray[x][y].shape) == 2: imgArray[x][y]= cv2.cvtColor( imgArray[x][y], cv2.COLOR_GRAY2BGR)
        imageBlank = np.zeros((height, width, 3), np.uint8)
        hor = [imageBlank]*rows
        hor_con = [imageBlank]*rows
        for x in range(0, rows):
            hor[x] = np.hstack(imgArray[x])
        ver = np.vstack(hor)
    else:
        for x in range(0, rows):
            if imgArray[x].shape[:2] == imgArray[0].shape[:2]:
                imgArray[x] = cv2.resize(imgArray[x], (0, 0), None, scale, scale)
            else:
                imgArray[x] = cv2.resize(imgArray[x], (imgArray[0].shape[1], imgArray[0].shape[0]), None,scale, scale)
            if len(imgArray[x].shape) == 2: imgArray[x] = cv2.cvtColor(imgArray[x], cv2.COLOR_GRAY2BGR)
        hor= np.hstack(imgArray)
        ver = hor
    return ver
  1. stackImages(缩放比例,水平拼接,垂直拼接)
stackImages(0.5,([img,img,img]))
stackImages(0.5,([img,img,img],[img,img,img]))

颜色检测

  • 转换为HSV空间
cv2.cvtColor(对象,cv2.COLOR_BGR2HSV)
  • 设置颜色调节器
  1. 定义一个窗口
cv2.namedWindow("窗口名")
  1. 设置窗口大小
cv2.resizeWindow("对应窗口名",宽度,高度)
  1. 创建滑动控制器
def empty():
    pass
cv2.namedWindow("TrackBars")
cv2.resizeWindow("TrackBars", 640, 240)
cv2.createTrackbar("Hue Min", "TrackBars", 0, 179, empty)
cv2.createTrackbar("Hue Max", "TrackBars", 179, 179, empty)
cv2.createTrackbar("Sat Min", "TrackBars", 0, 255, empty)
cv2.createTrackbar("Sat Max", "TrackBars", 255, 255, empty)
cv2.createTrackbar("Val Min", "TrackBars", 0, 255, empty)
cv2.createTrackbar("Val Max", "TrackBars", 255, 255, empty)
  • 将颜色调节器和HSV图像进行关联
  1. 获取控制器值
cv2.getTrackbarPos("控制器名","相应窗口名")
    h_min = cv2.getTrackbarPos("Hue Min", "TrackBars")
    h_max = cv2.getTrackbarPos("Hue Max", "TrackBars")
    s_min = cv2.getTrackbarPos("Sat Min", "TrackBars")
    s_max = cv2.getTrackbarPos("Sat Max", "TrackBars")
    v_min = cv2.getTrackbarPos("Val Min", "TrackBars")
    v_max = cv2.getTrackbarPos("Val Max", "TrackBars")
  1. 设置过滤图像

(1)最低阀值

numpy.array([h_min, s_min, v_min])
lower = np.array([h_min, s_min, v_min])

(2)最高阀值

numpy.array([h_max, s_max, v_max])
upper = np.array([h_max, s_max, v_max])

(3)阀值限制图像

cv2.inRange(HSV图像对象,最低阀值,最高阀值)
mask = cv2.inRange(imgHSV, lower, upper)
  • 获取检测颜色范围
  • 将所有颜色调节器的最低值设置好
  • 进行按位操作合成新图像
cv2.bitwise_and(原图像对象,原图像对象,mask=阀值限制图像)
imgResult = cv2.bitwise_and(img, img, mask=mask)
  • 图像拼接显示
import cv2
import numpy as np

img = cv2.imread("Resources/3.png")

#图像拼接函数
def stackImages(scale,imgArray):
    rows = len(imgArray)
    cols = len(imgArray[0])
    rowsAvailable = isinstance(imgArray[0], list)
    width = imgArray[0][0].shape[1]
    height = imgArray[0][0].shape[0]
    if rowsAvailable:
        for x in range ( 0, rows):
            for y in range(0, cols):
                if imgArray[x][y].shape[:2] == imgArray[0][0].shape [:2]:
                    imgArray[x][y] = cv2.resize(imgArray[x][y], (0, 0), None, scale, scale)
                else:
                    imgArray[x][y] = cv2.resize(imgArray[x][y], (imgArray[0][0].shape[1], imgArray[0][0].shape[0]), None, scale, scale)
                if len(imgArray[x][y].shape) == 2: imgArray[x][y]= cv2.cvtColor( imgArray[x][y], cv2.COLOR_GRAY2BGR)
        imageBlank = np.zeros((height, width, 3), np.uint8)
        hor = [imageBlank]*rows
        hor_con = [imageBlank]*rows
        for x in range(0, rows):
            hor[x] = np.hstack(imgArray[x])
        ver = np.vstack(hor)
    else:
        for x in range(0, rows):
            if imgArray[x].shape[:2] == imgArray[0].shape[:2]:
                imgArray[x] = cv2.resize(imgArray[x], (0, 0), None, scale, scale)
            else:
                imgArray[x] = cv2.resize(imgArray[x], (imgArray[0].shape[1], imgArray[0].shape[0]), None,scale, scale)
            if len(imgArray[x].shape) == 2: imgArray[x] = cv2.cvtColor(imgArray[x], cv2.COLOR_GRAY2BGR)
        hor= np.hstack(imgArray)
        ver = hor
    return ver

#定义检测颜色的范围
#设置颜色调节器
def empty():
    pass
cv2.namedWindow("TrackBars")
cv2.resizeWindow("TrackBars", 640, 240)
cv2.createTrackbar("Hue Min", "TrackBars", 0, 179, empty)
cv2.createTrackbar("Hue Max", "TrackBars", 19, 179, empty)
cv2.createTrackbar("Sat Min", "TrackBars", 110, 255, empty)
cv2.createTrackbar("Sat Max", "TrackBars", 240, 255, empty)
cv2.createTrackbar("Val Min", "TrackBars", 153, 255, empty)
cv2.createTrackbar("Val Max", "TrackBars", 255, 255, empty)

#关联颜色调节器和HSV图片
while True:
    #转换为HSV空间
    imgHSV = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
    h_min = cv2.getTrackbarPos("Hue Min", "TrackBars")
    h_max = cv2.getTrackbarPos("Hue Max", "TrackBars")
    s_min = cv2.getTrackbarPos("Sat Min", "TrackBars")
    s_max = cv2.getTrackbarPos("Sat Max", "TrackBars")
    v_min = cv2.getTrackbarPos("Val Min", "TrackBars")
    v_max = cv2.getTrackbarPos("Val Max", "TrackBars")
    print(h_min, h_max, s_min, s_max, v_min, v_max)
    lower = np.array([h_min, s_min, v_min])
    upper = np.array([h_max, s_max, v_max])
    mask = cv2.inRange(imgHSV, lower, upper)
    imgResult = cv2.bitwise_and(img, img, mask=mask)

    # cv2.imshow("windows", img)
    # cv2.imshow("imgHSV", imgHSV)
    # cv2.imshow("imgMask", mask)
    # cv2.imshow("imgResult", imgResult)

    imgStack = stackImages(0.6, ([img, imgHSV], [mask, imgResult]))
    cv2.imshow("Stacked", imgStack)
    cv2.waitKey(1)

轮廓检测

  • 转为灰度图像
cv2.cvtColor(对象,cv2.COLOR_BGR2GRAY)
  • 对灰度图像进行模糊处理
cv2.GaussianBlur(灰度图像对象,(7,7),1)
  • 边缘检测
cv2.Canny(模糊图像对象,50,50)
  • 定义空白图像
numpy.zeros_link(对象)
  • 定义轮廓处理函数
  1. 检索轮廓
cv2.findContours(对象,cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
  1. 遍历轮廓
for cnt in contours
  1. 定位区域
cv2.contourArea(cnt)
  1. 检索最小区域
if area > 500
  1. 绘制轮廓区域
 cv2.drawContours(imgContour, cnt, -1, (255, 0, 0), 3)
  1. 计算曲线长度
cv2.arcLength(cnt, True)
  1. 计算拐角点
cv2.approxPolyDP(cnt,0.02*peri,True)
  1. 创建对象点
objCor=len(approx)
  1. 获取边界框边界
cv2.boundingRect(approx)
  1. 将对象进行分类
if objCor == 3 : objectType = "Tri"
            else: objectType="None"
  1. 绘制矩形边界框
cv2.rectangle(imgContour, (x, y), (x+w, y+h), (0, 255, 0), 3)
  1. 贴上分类标签
cv2.putText(imgContour, objectType, (x+(w//2)-10, y+(h//2)-10), cv2.FONT_HERSHEY_COMPLEX, 0.7, (0, 0, 0), 2)
#轮廓处理函数
def getContours(img):
    #检索轮廓
    contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    for cnt in contours:
        #定位区域
        area = cv2.contourArea(cnt)
        print(area)
        #检索最小区域
        if area > 500:
            # 绘制轮廓区域
            cv2.drawContours(imgContour, cnt, -1, (255, 0, 0), 3)
            #计算曲线长度
            peri = cv2.arcLength(cnt, True)
            #print(peri)
            #计算拐角点
            approx = cv2.approxPolyDP(cnt, 0.02 * peri, True)
            print(len(approx))
            #创建对象角
            objCor = len(approx)
            #获取边界框边界
            x, y, w, h = cv2.boundingRect(approx)
            # 将对象进行分类
            if objCor == 3 : objectType = "Tri"
            elif objCor == 4:
                aspRatio = w/float(h)
                if aspRatio > 0.95 and aspRatio< 1.05: objectType = "Square"
                else: objectType = "Rectangle"
            elif objCor > 4: objectType = "Circles"
            else: objectType="None"
            #绘制矩形边界框
            cv2.rectangle(imgContour, (x, y), (x+w, y+h), (0, 255, 0), 3)
            cv2.putText(imgContour, objectType, (x+(w//2)-10, y+(h//2)-10), cv2.FONT_HERSHEY_COMPLEX, 0.7, (0, 0, 0), 2)
import cv2
import numpy as np

#图像拼接函数
def stackImages(scale,imgArray):
    rows = len(imgArray)
    cols = len(imgArray[0])
    rowsAvailable = isinstance(imgArray[0], list)
    width = imgArray[0][0].shape[1]
    height = imgArray[0][0].shape[0]
    if rowsAvailable:
        for x in range ( 0, rows):
            for y in range(0, cols):
                if imgArray[x][y].shape[:2] == imgArray[0][0].shape [:2]:
                    imgArray[x][y] = cv2.resize(imgArray[x][y], (0, 0), None, scale, scale)
                else:
                    imgArray[x][y] = cv2.resize(imgArray[x][y], (imgArray[0][0].shape[1], imgArray[0][0].shape[0]), None, scale, scale)
                if len(imgArray[x][y].shape) == 2: imgArray[x][y]= cv2.cvtColor( imgArray[x][y], cv2.COLOR_GRAY2BGR)
        imageBlank = np.zeros((height, width, 3), np.uint8)
        hor = [imageBlank]*rows
        hor_con = [imageBlank]*rows
        for x in range(0, rows):
            hor[x] = np.hstack(imgArray[x])
        ver = np.vstack(hor)
    else:
        for x in range(0, rows):
            if imgArray[x].shape[:2] == imgArray[0].shape[:2]:
                imgArray[x] = cv2.resize(imgArray[x], (0, 0), None, scale, scale)
            else:
                imgArray[x] = cv2.resize(imgArray[x], (imgArray[0].shape[1], imgArray[0].shape[0]), None,scale, scale)
            if len(imgArray[x].shape) == 2: imgArray[x] = cv2.cvtColor(imgArray[x], cv2.COLOR_GRAY2BGR)
        hor= np.hstack(imgArray)
        ver = hor
    return ver

#轮廓处理函数
def getContours(img):
    #检索轮廓
    contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    for cnt in contours:
        #定位区域
        area = cv2.contourArea(cnt)
        print(area)
        #检索最小区域
        if area > 500:
            # 绘制轮廓区域
            cv2.drawContours(imgContour, cnt, -1, (255, 0, 0), 3)
            #计算曲线长度
            peri = cv2.arcLength(cnt, True)
            #print(peri)
            #计算拐角点
            approx = cv2.approxPolyDP(cnt, 0.02 * peri, True)
            print(len(approx))
            #创建对象角
            objCor = len(approx)
            #获取边界框边界
            x, y, w, h = cv2.boundingRect(approx)
            # 将对象进行分类
            if objCor == 3 : objectType = "Tri"
            elif objCor == 4:
                aspRatio = w/float(h)
                if aspRatio > 0.95 and aspRatio< 1.05: objectType = "Square"
                else: objectType = "Rectangle"
            elif objCor > 4: objectType = "Circles"
            else: objectType="None"
            #绘制矩形边界框
            cv2.rectangle(imgContour, (x, y), (x+w, y+h), (0, 255, 0), 3)
            cv2.putText(imgContour, objectType, (x+(w//2)-10, y+(h//2)-10), cv2.FONT_HERSHEY_COMPLEX, 0.7, (0, 0, 0), 2)


img = cv2.imread("Resources/3s.png")
imgContour = img.copy()
#转换为灰度图像
imgGray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#模糊图像
imgBlur = cv2.GaussianBlur(imgGray, (7, 7), 1)
#边缘检测
imgCanny = cv2.Canny(imgBlur, 50, 50)

getContours(imgCanny)
#定义空白图像
imgBlank = np.zeros_like(img)
imgStack = stackImages(0.6, ([img, imgGray, imgBlur], [imgCanny, imgContour, imgBlank]))

cv2.imshow("imgStack", imgStack)

cv2.waitKey(0)

颜色追踪

  • 引入摄像头监控
import cv2
frameWidth = 640
frameHeight = 480
cap = cv2.VideoCapture(0)
cap.set(3, frameWidth)
cap.set(4, frameHeight)
cap.set(10, 150)
while True:
    success, img = cap.read()
    cv2.imshow("Result", img)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
  • 获取检测颜色值
import cv2
import numpy as np

frameWidth = 640
frameHeight = 480
cap = cv2.VideoCapture(0)
cap.set(3, frameWidth)
cap.set(4, frameHeight)
cap.set(10,150)

def empty(a):
    pass

cv2.namedWindow("HSV")
cv2.resizeWindow("HSV",640,240)
cv2.createTrackbar("HUE Min","HSV",0,179,empty)
cv2.createTrackbar("SAT Min","HSV",0,255,empty)
cv2.createTrackbar("VALUE Min","HSV",0,255,empty)
cv2.createTrackbar("HUE Max","HSV",179,179,empty)
cv2.createTrackbar("SAT Max","HSV",255,255,empty)
cv2.createTrackbar("VALUE Max","HSV",255,255,empty)

while True:

    _, img = cap.read()
    imgHsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)

    h_min = cv2.getTrackbarPos("HUE Min","HSV")
    h_max = cv2.getTrackbarPos("HUE Max", "HSV")
    s_min = cv2.getTrackbarPos("SAT Min", "HSV")
    s_max = cv2.getTrackbarPos("SAT Max", "HSV")
    v_min = cv2.getTrackbarPos("VALUE Min", "HSV")
    v_max = cv2.getTrackbarPos("VALUE Max", "HSV")
    print(h_min)

    lower = np.array([h_min,s_min,v_min])
    upper = np.array([h_max,s_max,v_max])
    mask = cv2.inRange(imgHsv,lower,upper)
    result = cv2.bitwise_and(img,img, mask = mask)

    mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
    hStack = np.hstack([img,mask,result])
    cv2.imshow('Horizontal Stacking', hStack)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()
  • 颜色检测函数
myColors = [[0, 118, 140, 179, 188, 255],
            [133, 56, 0, 159, 156, 255],
            [57, 56, 0, 100, 255, 255]]

def findColor(img, myColors):
    imgHSV = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    for color in myColors:
        lower = np.array(color[0:3])
        upper = np.array(color[3:6])
        mask = cv2.inRange(imgHSV, lower, upper)
        cv2.imshow("img", mask)
  • 将颜色检测和摄像头监控结合
success, img = cap.read()
findColor(img, myColors)
  • 获取检测对象轮廓
def getContours(img):
    contours,hierarchy = cv2.findContours(img,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
    x, y, w, h = 0, 0, 0, 0
    for cnt in contours:
        area = cv2.contourArea(cnt)
        if area>500:
            cv2.drawContours(imgResult, cnt, -1, (255, 0, 0), 3)
            peri = cv2.arcLength(cnt,True)
            approx = cv2.approxPolyDP(cnt,0.02*peri,True)
            x, y, w, h = cv2.boundingRect(approx)
    return x+w//2, y
  • 绘制物体运动轨迹
def drawOnCanvas(myPoints, myColorValues):
    for point in myPoints:
        cv2.circle(imgResult, (point[0], point[1]), 10, myColorValues[point[2]], cv2.FILLED)
import cv2
import numpy as np
frameWidth = 640
frameHeight = 480
cap = cv2.VideoCapture(0)
cap.set(3, frameWidth)
cap.set(4, frameHeight)
cap.set(10, 150)

myColors = [[0, 118, 140, 179, 188, 255],
            [133, 56, 0, 159, 156, 255],
            [57, 56, 0, 100, 255, 255]]

myColorValues = [[51, 51, 255],     ##BGR
                 [255, 0, 255],
                 [0, 255, 0]]

myPoints = []  ##[x, y, colorId]
def findColor(img, myColors, myColorValues):
    imgHSV = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    count = 0
    newPoints = []
    for color in myColors:
        lower = np.array(color[0:3])
        upper = np.array(color[3:6])
        mask = cv2.inRange(imgHSV, lower, upper)
        x, y = getContours(mask)
        cv2.circle(imgResult, (x, y), 10, myColorValues[count], cv2.FILLED)
        if x!= 0 and y!= 0:
            newPoints.append([x, y, count])
        count += 1
        # cv2.imshow(str(color[0]), mask)
        return newPoints

def drawOnCanvas(myPoints, myColorValues):
    for point in myPoints:
        cv2.circle(imgResult, (point[0], point[1]), 10, myColorValues[point[2]], cv2.FILLED)

def getContours(img):
    contours,hierarchy = cv2.findContours(img,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
    x, y, w, h = 0, 0, 0, 0
    for cnt in contours:
        area = cv2.contourArea(cnt)
        if area>500:
            cv2.drawContours(imgResult, cnt, -1, (255, 0, 0), 3)
            peri = cv2.arcLength(cnt,True)
            approx = cv2.approxPolyDP(cnt,0.02*peri,True)
            x, y, w, h = cv2.boundingRect(approx)
    return x+w//2, y

while True:
    success, img = cap.read()
    imgResult = img.copy()
    newPoints = findColor(img, myColors, myColorValues)
    if len(newPoints) != 0:
        for newP in newPoints:
            myPoints.append(newP)
            for i in myPoints:
                print("myPoints"+str(i))
    if len(myPoints) !=0:
        drawOnCanvas(myPoints, myColorValues)
    cv2.imshow("Result", imgResult)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
查看原文

赞 1 收藏 1 评论 0

dr526 发布了文章 · 2020-11-21

解决Ubuntu配置nginx出现的问题

Ubuntu18.04配置nginx出现的各种错误

  1. 缺少pcre库

    编译nginx

    在这里插入图片描述

出现错误

在这里插入图片描述

安装pcre库,出现错误

在这里插入图片描述

手动编译安装pcre库

(1)下载并解压pcre库

wget https://ftp.pcre.org/pub/pcre/pcre-8.43.tar.gz
tar -xvf pcre-8.43.tar.gz

在这里插入图片描述

(2)编译安装pcre库

cd pcre-8.43
sudo ./configure
sudo make
sudo make install

重新编译nginx

#在nginx-1.12.2目录下
sudo ./configure --with-stream

命令执行成功

在这里插入图片描述

  1. 出现"struct crypt_data"没有名为"current_salt"成员的错误

    执行make命令

    sudo make && make install

    出现"struct crypt_data"没有名为"current_salt"成员的错误

    在这里插入图片描述

解决方案:进入相应路径,将源码的第36行注释

sudo vi src/os/unix/ngx_user.c

在这里插入图片描述

重新执行sudo make && make install命令

  1. 出现-Werror=cast-function-type错误

在这里插入图片描述

解决方案

#进入nginx-1.12.2目录下的objs目录
cd objs
#修改Makefile文件
sudo vi Makefile 

在这里插入图片描述

重新回到nginx-1.12.2目录下执行sudo make && make install命令

  1. make命令出现权限不够错误

    在这里插入图片描述

进入root模式执行命令

sudo su #进入root模式
make && make install
  1. nginx启动出现无法连接pcre库错误

    在这里插入图片描述

查看依赖库

在这里插入图片描述

到/usr/local/lib目录下查看

在这里插入图片描述

设置软连接

#回到nginx下的sbin目录
cd /usr/local/nginx/sbin
#设置软连接
ln -s /usr/local/lib/libpcre.so.1.2.11 libpcre.so.1
#设置LD_LIBRARY_PATH(注:这种方法,每次开启nginx都需要重新设置LD_LIBRARY_PATH)
export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH

重新启动nginx

./nginx
# 查看服务是否正常启动
netstat -tanp

在这里插入图片描述

查看原文

赞 0 收藏 0 评论 0

dr526 发布了文章 · 2020-11-03

浅析I/O模型-select、poll、epoll

I/O流

  1. 概念

    (1)c++中将数据的输入输出称之为流(stream),在c++中,流被定义为类,成为流类(stream class),其定义的对象为流对象。

    (2)文件,套接字(socket),管道(pipe)等能够进行I/O操作的对象,可以被看做为流

  2. 工作机制

    (1)大多数文件系统的默认I/O操作都是缓存I/O。在Linux的缓存I/O机制中,读取数据时,都会将数据先拷贝到操作系统内核的缓冲区中,然后将操作系统内核缓冲区的数据拷贝到应用程序的地址空间,写的过程则相反。

    (2)缓存I/O使用操作系统内核缓冲区,在一定程度上分离了应用程序空间和实际的物理设备,通过将数据写入缓冲区后,再一次性处理,减少了读盘的次数,从而提高了性能

I/O模型

  1. 同步与异步:关注的是消息通信机制

    同步(synchronous):调用者会一直“等待”被调用者返回消息,才能继续执行,在此期间,调用者不能做其它事

    异步(asynchronous):被调用者通过状态、通知或回调机制主动通知调用者被调用者的运行状态,在此期间,调用者可以边“等待”,边做其它事

  2. 阻塞和非阻塞:关注调用者的状态

    阻塞(blocking):调用者一直“等待”所处的状态

    非阻塞(blocking):调用者能够边“等待”,边做其它事的状态

  3. 同步I/O

    (1)阻塞式I/O:程序发出I/O请求,如果内核缓冲区为空,此时进行读操作,那么该程序就会阻塞

    (2)非阻塞式I/O:程序发出I/O请求,如果内核缓冲区为空,此时进行读操作,此时就会立刻返回一个错误

    (3)I/O复用

    a. 这是一种机制,程序注册一组文件描述符给操作系统,监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。表示“我要监视这些fd是否有I/O事件发生,有了就告诉程序处理”。

    b. 当多个I/O流共用一个等待机制时,该模型会阻塞进程,但是进程时阻塞在这种机制的系统调用上,不是阻塞在真正的I/O操作上

    c. I/O多路复用需要和非阻塞I/O一起使用,非阻塞I/O和I/O多路复用式相对独立的。非阻塞I/O仅仅指流对象立刻返回,不会被阻塞;而I/O多路复用只是操作系统提供的一种便利的通知机制。

    (4)信号驱动式I/O

    a. 用户进程可以通过系统调用注册一个信号处理程序,然后主程序可以继续向下执行,当有I/O操作准备就绪时,由内核通知触发一个SIGIO信号处理程序执行,然后将用户进程所需要的数据从内核空间拷贝到用户空间

    b. 此模型的优势在于等待数据报到达期间进程不被阻塞。用户主程序可以继续执行,只要等待来自信号处理函数的通知。

  4. 异步I/O

    a. 程序进程向内核发送I/O调用后,不用等待内核响应,可以继续接受其他请求,内核调用的I/O如果不能立即返回,内核会继续处理其他事物,直到I/O完成后将结果通知给内核

    b. 信号驱动式IO是由内核通知我们何时启动一个IO操作,而异步IO是由内核通知我们IO操作何时完成。

I/O复用模型

  1. select

    select的大致工作流程:

    (1)采用数组组织文件描述符

    (2)通过遍历数组的方式,监视文件描述符的状态(可读,可写,异常)

    (3)如果没有可读/可写的文件描述符,进程会阻塞等待一段事件,超时就返回

    (4)当有一个可读/可写的文件描述符存在时,进程会从阻塞状态醒来

    (5)进行无差别轮询,找出能够操作的I/O流,若处理后,会移除对应的文件描述符

    select的缺点:

    (1)每次调用select,都需要把文件描述符集合从用户空间贝到内核空间,这个开销在I/O流很多时会很大

    (2)同时每次调用select都需要在内核遍历传递进来的所文件描述符数组,这个开销在I/O流很多时也很大

    (3)select支持的文件描述符数量太小了,默认是1024

  2. poll

    (1)采用链表组织文件描述符

    (2)原理和select一致

    (3)只是解决了支持的文件描述符受限的缺点

    (4)select和poll都是水平触发:找到可操作的I/O流并通知进程,但进程本次没有处理,文件描述符没有被移除,下次轮询时依旧会通知

  3. epoll

    工作原理:

    (1)红黑树和就绪链表,红黑树用于管理所有的文件描述符,就绪链表用于保存有事件发生的文件描述符。

    (2)接收到I/O请求,会在红黑树查找是否存在,不存在就添加到红黑树中,存在则将对应的文件描述符放入就绪链表中

    (3)如果就绪链表为空,进程则阻塞否则遍历就绪链表,并通知应用进程处理文件描述符对应的I/O

    工作模式:

    (1)LT模式(水平触发):检测到可处理的文件描述符时,通知应用程序,应用程序可以不立即处理该事件。后续会再次通知

    (2)ET模式(边缘触发):检测到可处理的文件描述符时,通知应用程序,应用程序必须立即处理该事件。如果本次不处理,则后续不再通知

参考资料

IO五种模型和select与epoll工作原理(引入nginx) - osc_1ont5xz2的个人空间 - OSCHINA - 中文开源技术交流社区

IO模型:同步、异步、阻塞、非阻塞 | 神奕的博客 (songlee24.github.io)

(3) io复用与epoll模型详解_个人文章 - SegmentFault 思否

(3) Linux IO模式及 select、poll、epoll详解_人云思云 - SegmentFault 思否

Linux 网络编程的5种IO模型:多路复用(select/poll/epoll) - 黄树超 - 博客园 (cnblogs.com)

(3) 网络编程——select模型(总结)_个人文章 - SegmentFault 思否

网络编程之IO模型与Epoll - 简书 (jianshu.com)

彻底搞懂epoll高效运行的原理 - 简书 (jianshu.com)

如果这篇文章说不清epoll的本质,那就过来掐死我吧! (3) - 知乎 (zhihu.com)

查看原文

赞 0 收藏 0 评论 0

dr526 发布了文章 · 2020-10-25

操作系统导论(1)

CPU虚拟化

  • 什么是虚拟化?

物理概念上不存在,但逻辑概念上是存在的。
比如:宾馆的房间,当你使用的时候,别人是无法使用的,在这段时间内,它是独属于你,同理,在其它用户使用期间,它是独属于其它用户的。换句话说,宾馆在物理意义上只有一间,在每个用户使用期间,它都是独属于每个用户,就好像每个人都拥有一间房间一样,即:逻辑上存在很多间房间

  • 为什么需要虚拟化?

计算机的各种资源有限,但是实际情况下,用户往往希望计算机能“同时”运行多个程序。

  • 如何实现虚拟化?
  1. 时分复用——分时复用

为每个程序建立至少一个进程,让所有进程进行并发执行。
即:操作系统把CPU时间划分为多个均等的时间片。例如:每5ms一个时间片,在每个时间片内运行一个进程。当进程的时间片用完被切换后,过一段时间会重新接着运行。

  1. 空分复用

由于计算机的内存资源有限,操作系统会惰性执行进程,即:仅在程序执行期间需要加载的代码或数据片段才会被加载,运行完毕后,操作系统会将该部分换出,再重新加载另外一部分。

进程

  • 什么是进程?
  1. 进程是程序的一次执行
  2. 进程是一个程序及其数据在处理机上顺序执行时所发生的活动
  3. 进程是具有独立功能的程序在一个数据集合上运行的过程,它是系统进行资源分配调度的一个独立单位
  • 进程的组成
  1. 进程控制块(Process Control Block,PCB):存储进程的调度信息、控制信息、处理机状态等,位于操作系统内核(kernel)中,受到保护,无法查看。
  2. 程序段
  3. 相关的数据段
  • 程序如何转化为进程
  1. 将代码和静态数据加载(load)到内存
  2. 为程序的运行时栈(run-time stack)分配一些内存用于存放局部变量、函数参数和返回地址
  3. 可能会为程序的堆分配一些内存。在C程序中,堆用于显示请求的动态分配数据。如:通过malloc()或者realloc()请求空间,并通过free()释放空间
  4. 执行一些其他初始化任务,特别是和输入/输出相关的任务

在这里插入图片描述

  • 进程的状态

运行(running):进程获得CPU,程序正在执行的状态
就绪(ready):进程已处于准备号运行的状态,即:进程已分配到除CPU以外的所有必要资源后,只需要再获得CPU,便可立即执行
阻塞(blocked):正在执行的进程由于发生某事件(如I/O请求,申请缓冲器失败等)暂时无法继续执行的状态,即:进程的执行收到阻塞
创建状态:系统没有足够的内存让内存装入其中,进程的创建工作没有完成,进程无法被调度执行
终止状态:系统可以处于已退出但尚未清理(将PCB清0,并将PCB空间返还系统)的状态
在这里插入图片描述

  • 进程是受限直接执行的
  1. 什么是受限直接执行?

    “直接执行”:只需要在CPU上运行

    “受限”:进程的操作收到操作系统的限制,比如:向磁盘发出I/O请求或者获得更多的系统资源(比如:CPU或者内存)

  2. 受限直接执行的原因?

    “直接执行”:为了让程序尽可能快的执行

    “受限”:避免进程执行一些危险的操作

  3. 如何实现受限直接执行

    • 实现“受限”
      (1)用户模式(user mode):用户模式下,运行的代码会受到限制。比如:在用户模式下运行,进程不能发出I/O请求。
      (2)内核模式(kernel mode):和用户模式相对,操作系统(或内核)都是在这种模式下运行
      (3)系统调用:用户希望执行某种特权操作(比如:从硬盘读取数据),硬件为用户程序提供了执行系统调用的能力,允许内核小心地向用户程序暴露某些关键功能,例如:访问文件系统,创建和销毁进程、和其它进程通信,以及分配更多的内存)
      (4)陷阱(trap)指令:要执行系统调用就必须执行陷阱指令,该指令在被操作系统加载到内核的同时将用户模式提升为内核模式。完成后,操作系统会调用一个从陷阱返回指令(return-from-trap),回到用户模式
      (5)陷阱表(trap table):计算机启动时,操作系统就会初始化陷阱表,并且CPU会记住它的位置。当执行陷阱指令时,CPU就会根据陷阱表找到需要运行的指令。

在这里插入图片描述

  • 实现直接执行——实现进程切换
    (1)操作系统获取CPU的控制权
    ​ a. 协作方式(等待系统调用):操作系统等待进程进行系统调用或者某种非法操作发生时,从而获得CPU控制权
    ​ b. 非协作方式(操作系统进行控制):通过时钟中断(timer interrupt),时钟设备可以每隔几秒钟产生一次中断,产生中断时,正在运行的程序停止,操作系统中预先配置的中断处理程序(iterrput handler)会运行,此时操作系统会重新获得CPU控制权
    (2)保存和恢复上下文
    ​ a. 调度程序(scheduler):决定继续运行当前正在运行的程序还是切换到另一个进程
    ​ b. 上下文切换:为当前正在运行的进程保存一些信息,并为即将执行的进程恢复一些信息(借助PCB实现)
    ​ c. 进行上下文切换时,操作系统会执行一些底层汇编代码,来保存通用寄存器(GR)、程序计数器(PC),以及当前正在运行进程的内核栈指针,然后恢复寄存器、程序计数器,并切换内核栈,供即将运行的进程使用。

在这里插入图片描述

  • 系统调用期间发生时钟中断怎么办?
  • 处理一个中断时发生另外一个中断怎么办?
查看原文

赞 0 收藏 0 评论 0

dr526 发布了文章 · 2020-10-25

操作系统导论(2)

进程的调度

  • 考虑因素

    1. 为了构建调度策略,需要做一些简化假设,这些假设和系统中运行的进程相关,统称为工作负载(workload)

      (1)每一个进程(工作)运行相同的时间

      (2)所有工作同时到达,有时候当多个工作到达的时间相差很小的时候,也近似认为是同时到达的

      (3)一旦开始工作,每个工作将保持运行直到完成

      (4)所有工作只是使用CPU,即:它们不执行I/O操作

      (5)每个工作的运行时间已知

    2. 为了能够衡量不同调度策略的优缺点,提出一个指标——周转时间(turnaround time)

      (1)定义:任务完成时间减去任务到达的时间,即:

在这里插入图片描述

 (2)当满足假设同时到达时,到达时间为0,周转时间等于完成时间

 (3)周转时间是一个**性能**(performance)**指标**。而性能和公平在调度系统往往时矛盾的。调度系统可以优化性能,但代价时阻止一些任务运行,这就降低了公平
  • 先进先出(FIFO)

    1. 先进先出(First In First Out):先就绪的工作先执行
    2. 假设有A、B、C三个工作,A比B早一点点,B比C早一点点,此时根据我们的假设,可以将A、B、C近似看作时同时到达的。但是根据实际情况,是A先执行,其次是B,最后是C。假设每个工作运行10s,求工作的平均周转时间(average turnaround time)?
      在这里插入图片描述
 A的周转时间为10s,B的周转时间为20s,C的周转时间为30s

 平均周转时间为(10+20+30)/3=20
    1. 现在放宽假设1,让A、B、C运行时间不同,考虑FIFO是否存在平均周转时间较长的情况
    2. 假设A、B、C三个工作,A运行时间为100s,B和C运行时间为10s,如果依旧是A先早到一点,然后是B,最后是C(仍然近似认为是同时到达的),此时系统的平均周转时间较长(100+110+120)/3=110

      在这里插入图片描述

    3. FIFO出现4这种情况被称为护航效应(convoy effect),即:一些耗时较少的潜在资源消耗者排在重量级的资源消费者后面。例如:在杂货店只有一个排队队伍的时候,你看见前面的装满了3辆购物车的货物时,这会让你等很长时间
    • 最短任务优先(SJF)

      1. 最短任务优先(Shortest Job First):先运行最短的时间,然后是次短的时间,如此继续
      2. 依旧在上述4的情况下,按照SJF的策略,平均周转时间为(10+20+120)/3=50,和FIFO相比,显著降低了平均周转时间。但前提是满足假设2——同时到达
        在这里插入图片描述
      3. 现在放宽假设2,即:工作能够随时到达,考虑SJF平均周转时间较长的情况
      4. 依旧是FIFO中4的情况,假设A在t=0时到达,并且需要运行100s,而B和C在t=10s到达,各自运行10s。则A的周转时间为100s,B的周转时间为110-10=100,C的周转时间为120-10=110。平均周转时间为(100+100+110)/3=103.33s
      5. 很明显当工作能够随时到达的情况下,SJF可能会出现平均周转时间较长的情况
    • 最短完成时间优先(STCF)

      1. 最短完成时间优先(Shortest Time-to-Completion First):放宽假设3,即:调度程序可以安排其它工作抢占正在运行的工作占用的CPU。
      2. 在SJF中添加了抢占,每当新工作进入就绪状态时,它就会确定剩余工作和新工作中,谁的完成时间最少,然后调度这个工作
      3. 在上述4的情况下,STCF将抢占A并先运行完B和C后,才会继续运行。则A的周转时间为120s,B的周转时间为10s,C的周转时间为20s,平均周转时间为(120+10+20)/3=50,显著降低了SJF相同情况下的平均周转时间

        在这里插入图片描述

    • 增加考虑因素

      1. 当符合假设4、5成立时,即:知道任务长度,并且任务只使用CPU,根据当前的唯一衡量指标为周转时间时,STCF是一个很好的策略。但是,引入分时系统时,就出现了问题,因为此时需要任务和用户进行交互,而周转时间无法衡量任务的交互性
      2. 响应时间(response time):能够衡量任务的交互性,定义为从任务到达系统到首次运行的时间
        在这里插入图片描述
    • 轮转(RR)

      1. 轮转(Round-Robin):在一个时间片内运行一个工作,然后切换到运行队列的下一个任务,而不是运行一个任务直到结束。它反复执行,直到所有任务完成。
      2. 时间片长度必须时时钟周期的倍数。如果不是,进行上下文切换的时候需要等待一段时间,此时CPU没工作,就浪费了CPU资源
      3. 假设3个任务,A、B、C在系统中同时到达,并且它们都希望运行5s,SJF调度程序必须运行完当前任务才能运行下一个任务,而1s时间片的RR能够快速循环工作。RR平均响应时间为:(0+1+2)/3=1(注:同时到达,到达时间为0),SJF算法平均响应时间为(0+5+10)/3=5

    在这里插入图片描述

    1. 时间片长度对RR至关重要,越短,RR在响应时间上的表现越好,但是时间片不能设置得太短:突然上下文切换会影响整体性能。因为上下文切换的成本不仅仅来自保存和恢复少量寄存器的操作系统操作。程序在运行时,还会在CPU高速缓存、TLB、分支预测器和其他片上的硬件中建立了大量的状态。所以时间片长度需要慎重权衡,让它足够长,以便摊销上下文切换成本,而又不会让系统不及时响应
    2. 摊销(amortize):通过减少成本的频度(即:执行较少的操作),系统的总成本就会降低。例如:如果时间片设置为10ms,并且上下文切换时间为1ms,大约会浪费10%的时间用于上下文切换。为了摊销这个成本,可以把时间片长度增加到100ms,则只有不到1%的时间会用于上下文切换。
    3. 在3中,我们只考虑了响应时间,没考虑周转时间,如果计算RR的周转时间,A为13,B为14,C为15,平均14。而SJF的周转时间为,A为5,B为10,C为15,平均10.此时RR虽然响应时间较好,但是周转时间较差。
    4. 到目前为止。有两类调度程序

      (1)SJF、STCF优化了周转时间,但是响应时间——交互性不好

      (2)RR优化了响应时间,但是周转时间不好

    • 结合I/O

      1. 放宽假设4,即:工作会执行I/O,此时调度程序会面临两个问题

        (1)发起I/O请求做出决定,因为当前运行的任务在I/O期间不会使用CPU,它会被阻塞等待I/O完成。这时调度程序需要考虑是否等待该任务的执行还是安排另一项任务

        (2)I/O完成时做出决定。I/O完成时会产生中断,操作系统运行并将发出I/O的进程从阻塞状态移回到就绪状态。此时调度程序将考虑是继续执行该任务,还是执行其他任务

      2. 假设有两项工作A、B,每项工作需要50ms的CPU时间。A每运行10ms,就会发出一次I/O请求,而B只是单纯地使用CPU50ms.调度程序先运行A再运行B。假设构建STCF调度程序。可以将A的每个10ms的子工作看作是一项独立的工作。所以任务运行时,先执行A10ms的子任务,完成后,会执行B,当I/O请求完成后,就会抢占B并运行10ms,这样就会充分利用系统。

        在这里插入图片描述

      3. 当交互式作业(即:I/O请求较多)正在执行I/O时,其他CPU密集型任务(即:I/O操作很少)将运行,从而更好的利用处理器
    • 多级反馈队列(MLFQ)

      1. 多级反馈队列(Multi-level Feedback Queue,MLFQ)需要解决的问题

        (1)优化周转时间

        (2)放宽假设5,即:不知道任务运行时间

        (3)降低响应时间,获取更好的交互体验

      2. 基本构成:有许多独立的队列,每个队列有不同的优先级(priority level)
      3. 基本规则:

        (1)规则1:如果A的优先级>B的优先级,运行A(不运行B)

        (2)规则2:如果A的优先级=B的优先级,轮转运行A和B

      4. 如何改变优先级(1)?

        (1)系统需要执行的任务可以分为下列两类

        ​ a. 运行时间很短、频繁放弃CPU的交互性任务

        ​ b. 需要很多CPU时间、响应时间不是很重要的长时间计算密集型任务

        (2)优先级调整算法

        ​ a. 规则3:任务进入系统时,放在最高优先级(最上层队列)

        ​ b. 规则4a:任务用完整个时间片后,降低优先级(移入下一个队列)

        ​ c. 规则4b:如果工作再其时间片内主动释放CPU,则优先级不变

        (3)实例1:单个长工作

        下图展示了一个有三个队列的调度程序。该工作首先进入最高优先级(Q2),执行10ms的时间片后,优先级-1,最终进入Q1,并一直到执行完毕

        在这里插入图片描述

     (4)实例2:来了一个短工作
    
     有两个工作:A时一个长时间运行的CPU密集任务,B是一个运行时间很短的交互型任务。假设A执行一段时间后B到达。下图中A(用黑色表示)在最低优先队列中(由(3)可知:长时间任务很长时间都会在最低队列中),B(用灰色表示)在时间T=100时到达,并加入最高优先级队列中。由于它运行时间很短,经过两个时间片,在被移入最低优先级队列之前,B执行完毕。然后A继续运行。
    
     ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200927185023424.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzMzI2NzQ0,size_16,color_FFFFFF,t_70#pic_center)
    
    
     (5)MLFQ算法的目标:如果不知道任务时短任务还是长任务,那么就在考试的时候假设它时短任务,并赋予最高优先级。如果确实是短任务,则会很快执行完毕。否则就会被慢慢移入低优先级队列,而这个时候该任务也被认为是长任务了。通过这种方式,MLFQ近似于SJF。
    
     (6)实例3:有I/O
    
     根据4b,交互型工作中有大量的I/O操作(比如等待用户的键盘或鼠标输入),它会在时间片用完之前放弃CPU,在这种情况下,我们会保持它的优先级不变
    
     假设交互型工作B(用灰色表示)每执行1ms便需要进行I/O操作,它与长时间运行的工作A(用黑色表示)竞争CPU。MLFQ算法保持B在最高优先级,因为B总是让CPU。如果B是交互型工作,MLFQ就进一步实现了它的目标,让交互型工作快速运行。
    
     
    
    1. 现在,MLFQ在长时间任务之间可以公平地分享CPU,又能给短工作或交互型工作很好地响应时间。这样就完美呢?其实还有问题

      a. 饥饿问题(starvation)问题。即:系统有许多交互型工作,就会不停地抢占长时间任务地CPU,让它们永远无法得到CPU

      b. 愚弄调度程序(game the scheduler)。即:进程在时间片用完之前,调用一个I/O操作(比如访问一个无关的文件),从而主动释放CPU。如此便可以保持吃在高优先级,占用更多的CPU时间

      c. 一个程序可能在不同时间表现不同。即:一个计算密集型的进程可能在某段时间表现为一个交互型的进程

    • 如何提高优先级(2)?

      1. 如何解决上述5中的问题?

        周期性地提升所有任务地优先级。最简单的就是周期性的将所有任务放到最高优先级队列中

        规则5:经过一段时间S,就将系统的任务重新加入到最高优先级队列中。

      2. 新规则解决的问题

        (1)进程不会饿死——在最高优先级队列中,它会以RR的方式,和其他高优先级工作分享CPU,从而最终获得执行

        (2)如果一个CPU密集型工作变成了交互型,当它的优先级提升,调度程序会正确对待它

      3. 下边有两张图,左边没有优先级提升,长时间任务在两个短任务到达后被饿死。右边的每隔50ms就有一次优先级提升(50ms只是举例),因此至少保证了长任务的工作会有一定的进程,每过50ms就被提升到最高优先级,从而定期获得执行。

        在这里插入图片描述

      4. 剩余问题

        (1)S的值如何设置?如果S设置得太高,长任务会被饿死,如果太低,交互型工作得不到合适的CPU时间比例

        (2)如何阻止调度程序被愚弄?工作在时间片以内释放CPU就保留它的优先级是存在问题的

    • 选择好的计时方式

      1. 更完善的CPU计时方式:调度程序应该记录一个进程在某一层消耗的总时间,只要进程用完了自己的配额,就将它降到低一级的优先级队列中。
      2. 重写规则4a和4b

        规则4:一旦工作用完了其在某一层的时间配额(无论中间主动放弃了多少次的CPU),就降低其优先级(移入低一级队列)

      3. 下图对比了在规则4a、4b的策略下(左图),以及在新的规则4(右图)的策略下,同样试图愚弄调度程序的进程的表现。灭有规则4的保护下,进程可以在每个时间片结束前发起一次I/O操作,从而垄断CPU时间,有了这样的保护,无论进程的I/O行为如何,都会慢慢降低优先级

        在这里插入图片描述

    • MLFQ调优以及其他问题

      问题:

      (1)配置多少队列

      (2)每一层队列的时间片配置多大

      这些问题没有显而易见的答案,只有利用对工作负载的经验以及后续对调优程序的调优,才会取得令人满意的平衡

      例如:高优先级队列通常只有较短的时间片,使得交互型工作能够更快的切换。而低优先级队列中更多的是CPU密集性工作,配置更长的时间片会更好

    • MLFQ规则小结

      (1)规则1:如果A的优先级>B的优先级,运行A(不运行B)

      (2)规则2:如果A的优先级=B的优先级,轮转运行A和B

      (3) 规则3:任务进入系统时,放在最高优先级(最上层队列)

      (4)规则4:一旦工作用完了其在某一层的时间配额(无论中间主动放弃了多少次的CPU),就降低其优先级(移入低一级队列)

      (5)规则5:经过一段时间S,就将系统的任务重新加入到最高优先级队列中。

    查看原文

    赞 0 收藏 0 评论 0

    dr526 发布了文章 · 2020-10-25

    操作系统导论(1)

    CPU虚拟化

    • 什么是虚拟化?

    物理概念上不存在,但逻辑概念上是存在的。
    比如:宾馆的房间,当你使用的时候,别人是无法使用的,在这段时间内,它是独属于你,同理,在其它用户使用期间,它是独属于其它用户的。换句话说,宾馆在物理意义上只有一间,在每个用户使用期间,它都是独属于每个用户,就好像每个人都拥有一间房间一样,即:逻辑上存在很多间房间

    • 为什么需要虚拟化?

    计算机的各种资源有限,但是实际情况下,用户往往希望计算机能“同时”运行多个程序。

    • 如何实现虚拟化?
    1. 时分复用——分时复用

    为每个程序建立至少一个进程,让所有进程进行并发执行。
    即:操作系统把CPU时间划分为多个均等的时间片。例如:每5ms一个时间片,在每个时间片内运行一个进程。当进程的时间片用完被切换后,过一段时间会重新接着运行。

    1. 空分复用

    由于计算机的内存资源有限,操作系统会惰性执行进程,即:仅在程序执行期间需要加载的代码或数据片段才会被加载,运行完毕后,操作系统会将该部分换出,再重新加载另外一部分。

    进程

    • 什么是进程?
    1. 进程是程序的一次执行
    2. 进程是一个程序及其数据在处理机上顺序执行时所发生的活动
    3. 进程是具有独立功能的程序在一个数据集合上运行的过程,它是系统进行资源分配调度的一个独立单位
    • 进程的组成
    1. 进程控制块(Process Control Block,PCB):存储进程的调度信息、控制信息、处理机状态等,位于操作系统内核(kernel)中,受到保护,无法查看。
    2. 程序段
    3. 相关的数据段
    • 程序如何转化为进程
    1. 将代码和静态数据加载(load)到内存
    2. 为程序的运行时栈(run-time stack)分配一些内存用于存放局部变量、函数参数和返回地址
    3. 可能会为程序的堆分配一些内存。在C程序中,堆用于显示请求的动态分配数据。如:通过malloc()或者realloc()请求空间,并通过free()释放空间
    4. 执行一些其他初始化任务,特别是和输入/输出相关的任务

    在这里插入图片描述

    • 进程的状态

    运行(running):进程获得CPU,程序正在执行的状态
    就绪(ready):进程已处于准备号运行的状态,即:进程已分配到除CPU以外的所有必要资源后,只需要再获得CPU,便可立即执行
    阻塞(blocked):正在执行的进程由于发生某事件(如I/O请求,申请缓冲器失败等)暂时无法继续执行的状态,即:进程的执行收到阻塞
    创建状态:系统没有足够的内存让内存装入其中,进程的创建工作没有完成,进程无法被调度执行
    终止状态:系统可以处于已退出但尚未清理(将PCB清0,并将PCB空间返还系统)的状态
    在这里插入图片描述

    • 进程是受限直接执行的
    1. 什么是受限直接执行?

      “直接执行”:只需要在CPU上运行

      “受限”:进程的操作收到操作系统的限制,比如:向磁盘发出I/O请求或者获得更多的系统资源(比如:CPU或者内存)

    2. 受限直接执行的原因?

      “直接执行”:为了让程序尽可能快的执行

      “受限”:避免进程执行一些危险的操作

    3. 如何实现受限直接执行

      • 实现“受限”
        (1)用户模式(user mode):用户模式下,运行的代码会受到限制。比如:在用户模式下运行,进程不能发出I/O请求。
        (2)内核模式(kernel mode):和用户模式相对,操作系统(或内核)都是在这种模式下运行
        (3)系统调用:用户希望执行某种特权操作(比如:从硬盘读取数据),硬件为用户程序提供了执行系统调用的能力,允许内核小心地向用户程序暴露某些关键功能,例如:访问文件系统,创建和销毁进程、和其它进程通信,以及分配更多的内存)
        (4)陷阱(trap)指令:要执行系统调用就必须执行陷阱指令,该指令在被操作系统加载到内核的同时将用户模式提升为内核模式。完成后,操作系统会调用一个从陷阱返回指令(return-from-trap),回到用户模式
        (5)陷阱表(trap table):计算机启动时,操作系统就会初始化陷阱表,并且CPU会记住它的位置。当执行陷阱指令时,CPU就会根据陷阱表找到需要运行的指令。

    在这里插入图片描述

    • 实现直接执行——实现进程切换
      (1)操作系统获取CPU的控制权
      ​ a. 协作方式(等待系统调用):操作系统等待进程进行系统调用或者某种非法操作发生时,从而获得CPU控制权
      ​ b. 非协作方式(操作系统进行控制):通过时钟中断(timer interrupt),时钟设备可以每隔几秒钟产生一次中断,产生中断时,正在运行的程序停止,操作系统中预先配置的中断处理程序(iterrput handler)会运行,此时操作系统会重新获得CPU控制权
      (2)保存和恢复上下文
      ​ a. 调度程序(scheduler):决定继续运行当前正在运行的程序还是切换到另一个进程
      ​ b. 上下文切换:为当前正在运行的进程保存一些信息,并为即将执行的进程恢复一些信息(借助PCB实现)
      ​ c. 进行上下文切换时,操作系统会执行一些底层汇编代码,来保存通用寄存器(GR)、程序计数器(PC),以及当前正在运行进程的内核栈指针,然后恢复寄存器、程序计数器,并切换内核栈,供即将运行的进程使用。

    在这里插入图片描述

    • 系统调用期间发生时钟中断怎么办?
    • 处理一个中断时发生另外一个中断怎么办?
    查看原文

    赞 0 收藏 0 评论 0

    dr526 发布了文章 · 2020-10-24

    c++11线程

    c++11线程基本用法

    • 简单示例

      1. thread和join()

        #include<iostream>
        #include<thread>
        using namespace std;
        
        void test() {
            cout << "我的线程执行" << endl;
            for (int i = 0; i < 10; ++i);
            cout << "我的线程执行完毕" << endl;
        }
        int main() {
            //thread是标准库的类,test为可调用对象,作为线程执行起点
            thread th(test);
            //join():阻塞主线程,让主线程等待子线程执行完毕
            th.join();
            cout << "主线程执行结束" << endl;
            return 0;
        }

        在这里插入图片描述

      2. detach()

        //传统多线程程序主线程要等待子线程执行完毕后才能退出
        //detach():将主线程和子线程分离。可以让主线程不必等待子线程
        //一旦detach()之后,与主线程关联的thread对象就会视区和主线程的关联,此时这个子线程就会在后台运行。当子线程执行完成后,由语和女性时库负责清理该线程相关资源
        //detach()会让子线程失去我们的控制,使用detach()后不能join()
        #include<iostream>
        #include<thread>
        using namespace std;
        
        void test() {
            cout << "我的线程执行" << endl;
            for (int i = 0; i < 10; ++i);
            cout << "我的线程执行完毕" << endl;
        }
        int main() {
            thread th(test);
            th.detach();
            cout << "主线程执行完毕" << endl;
            return 0;
        }

        在这里插入图片描述

     主线程执行完毕后,子线程由运行时库接管,无法打印出来
    
    1. joinable()

      //joinable():判断是否可以成功join()或者detach(),返回true或者false
      #include<iostream>
      #include<thread>
      using namespace std;
      
      void test() {
          cout << "我的线程执行" << endl;
          for (int i = 0; i < 10; ++i);
          cout << "我的线程执行完毕" << endl;
      }
      int main() {
          thread th(test);
          if (th.joinable()) {
              cout << "1:joinable()==true" << endl;
          }
          else {
              cout << "1:joinable()==false" << endl;
          }
          th.detach();
          if (th.joinable()) {
              cout << "2:joinable()==true" << endl;
          }
          else {
              cout << "2:joinable()==false" << endl;
          }
          cout << "主线程执行完毕" << endl;
          return 0;
      }

      在这里插入图片描述

    • 其他创建线程的方法

      1. 类对象作为可调用对象

        #include<iostream>
        #include<thread>
        using namespace std;
        
        class Test {
        public:
            void operator()() {//不能带参数
                cout << "我的线程开始执行" << endl;
                for (int i = 0; i < 10; ++i);
                cout << "我的线程执行完毕" << endl;
            }
        };
        
        int main() {
            Test test;
            thread th(test);
            th.join();
            cout << "主线程执行完毕" << endl;
            return 0;
        }

        若把join()改成detach(),主线程执行结束,test还在吗?

        如果对象不在了,线程还能继续执行吗?

        这个对象实际上是被复制到线程中去,执行完线程后,test会被销毁,但是复制的对象还在

        #include<iostream>
        #include<thread>
        using namespace std;
        
        class Test {
        public:
            Test(){
                cout << "Test()构造函数被执行" << endl;
            }
            Test(const Test& test) {
                cout << "拷贝构造函数执行" << endl;
            }
            ~Test() {
                cout << "析构函数执行" << endl;
            }
            void operator()() {//不能带参数
                cout << "线程开始执行" << endl;
                for (int i = 0; i < 10; ++i);
                cout << "线程执行完毕" << endl;
            }
        };
        
        int main() {
            Test test;
            thread th(test);
            th.detach();
            for (int i = 0; i < 10; ++i) {
                cout << "主线程执行完毕" << i << endl;
            }
            return 0;
        }

        在这里插入图片描述

     将detach()改为join()后
    
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201006200716787.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzMzI2NzQ0,size_16,color_FFFFFF,t_70#pic_center)
    
    
    1. 用lambda表达式

      #include<iostream>
      #include<thread>
      using namespace std;
      
      
      int main() {
          auto test = [] {
              cout << "线程开始执行" << endl;
              for (int i = 0; i < 10; ++i);
              cout << "线程执行完毕" << endl;
          };
          thread th(test);
          th.join();
          cout << "主线程执行完毕"  << endl;
          return 0;
      }
    • 传递临时对象作为线程参数

      1. 可调用对象带有参数时

        #include<iostream>
        #include<thread>
        using namespace std;
        
        //传入的不是myNum的引用,指向地址不同,实际是值传递,即便主线程detach(),子线程使用num不会出问题
        //传入的指针依旧是指向muBuf的地址,所以detach后,子线程会出问题。可以将char*改为const string&
        //使用const string&也有问题,即:不知道mybuf不知道什么时候转换为string,如果主线程执行完毕后,还没转换完毕,就有问题
        void test(const int& num, char* buf) {
            cout << "线程开始执行" << endl;
            cout << num << endl;
            cout << buf << endl;
            cout << "线程执行完毕" << endl;
        }
        
        int main() {
            int myNum = 1;
            int& num = myNum;
            char myBuf[] = "only for test!";
            thread th(test, num, myBuf);
            th.join();
            cout << "主线程执行完毕" << endl;
            return 0;
        }
        // 生成一个临时string对象,绑定const string&
        //使用这个方法,就会让string对象在主线程执行完毕前构造
        //在创建线程的同时构造临时对象是可行的
        thread th(test,num,string(myBuf));

        总结(针对detach())

        (1)若传递int这种简单类型参数,建议都是值传递,不要用引用

        (2)如果传递类对象,避免隐式类型转换。全部都要在创建线程就构建临时对象,然后函数参数中用引用,否则系统还会构造一次

      2. 线程id的概念

        (1)每个线程(包括主线程)实际都对应着一个数字,并且数字都不同

        (2)线程id可以用c++标准库中的函数来获取,即:std::this_thread::get_id()

      3. 临时对象构造时机抓捕

        #include<iostream>
        #include<thread>
        using namespace std;
        
        class A {
        public:
            int num;
            //类型转换构造函数,可以把一个int转换成一个类A对象
            A(int num) :num(num) {
                cout << "构造函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
            }
            A(const A& a) :num(a.num) {
                cout << "拷贝构造函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
            }
            ~A() {
                cout << "析构函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
            }
        };
        
        void test(const A& a) {
            cout << "线程test的参数地址是" << &a << "threadid=" << std::this_thread::get_id() << endl;
        }
        
        int main() {
            cout << "主线程id是" << std::this_thread::get_id() << endl;
            int num = 2;
            thread th(test, num);
            th.join();
            cout << "主线程执行完毕" << endl;
            return 0;
        }

        在这里插入图片描述

     A类对象在子线程中构造的,若改为detach()后,当主线程执行完毕后,还没构造,就出现问题了
    
     ```c++
     //在创建线程就构建临时对象
     thread th(test, A(num));
     ```
    
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201006200754483.png#pic_center)
    
    
    • 传递类对象、智能指针作为线程参数

      1. 线程参数为应用时修改值,不会影响主线程的值

        #include<iostream>
        #include<thread>
        using namespace std;
        
        class A {
        public:
            mutable int num;//标注为const也能修改
            //类型转换构造函数,可以把一个int转换成一个类A对象
            A(int num) :num(num) {
                cout << "构造函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
            }
            A(const A& a) :num(a.num) {
                cout << "拷贝构造函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
            }
            ~A() {
                cout << "析构函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
            }
        };
        
        void test(const A& a) {
            a.num = 199;//修改不会影响main的值
            cout << "a.num=" << a.num << endl;
            cout << "线程test的参数地址是" << &a << "threadid=" << std::this_thread::get_id() << endl;
        }
        
        int main() {
            cout << "主线程id是" << std::this_thread::get_id() << endl;
            A a(10);
            thread th(test, a);//将类对象作为线程参数
            th.join();
            cout << "主线程的a.num=" << a.num << endl;
            cout << "主线程执行完毕" << endl;
            return 0;
        }

        在这里插入图片描述

      2. std::ref函数,可以让线程传入的参数不被复制(注:没使用detach())

        //将线程传入参数加上std::ref
        thread th(test, std::ref(a));

        在这里插入图片描述

      3. 传递智能指针

        #include<iostream>
        #include<thread>
        using namespace std;
        
        void test(unique_ptr<int> uptr) {
            cout << "子线程id是" << std::this_thread::get_id() << endl;
            cout << "当前智能指针的地址是" << uptr << endl;
        }
        
        int main() {
            cout << "主线程id是" << std::this_thread::get_id() << endl;
            unique_ptr<int> uptr(new int(10));
            cout << "当前智能指针的地址为" << uptr << endl;
            thread th(test, std::move(uptr));//将类对象作为线程参数
            th.join();
            cout << "主线程执行完毕" << endl;
            return 0;
        }

        在这里插入图片描述

     若改为detach(),当主线程执行完毕后,uptr被释放,但子线程还在执行,此时就会出现问题
    
    • 用成员函数指针做线程函数

      #include<iostream>
      #include<thread>
      using namespace std;
      
      class A {
      public:
          mutable int num;
          //类型转换构造函数,可以把一个int转换成一个类A对象
          A(int num) :num(num) {
              cout << "构造函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
          }
          A(const A& a) :num(a.num) {
              cout << "拷贝构造函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
          }
          ~A() {
              cout << "析构函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
          }
          void thread_work(int num) {
              cout << "子线程thread_work执行了" << ",threadid=" << std::this_thread::get_id() << endl;
          }
      };
      
      void test(const A& a) {
          a.num = 199;//修改不会影响main的值
          cout << "a.num=" << a.num << endl;
          cout << "线程test的参数地址是" << &a << "threadid=" << std::this_thread::get_id() << endl;
      }
      
      int main() {
          cout << "主线程id是" << std::this_thread::get_id() << endl;
          A a(10);
          //第一个参数是对象函数指针,第二个参数对象,其后参数为函数所需参数
          thread th(&A::thread_work, a, 1);
          th.join();
          cout << "主线程的a.num=" << a.num << endl;
          cout << "主线程执行完毕" << endl;
          return 0;
      }

    c++11线程的互斥量

    • 创建和等待多个线程

      #include<iostream>
      #include<thread>
      #include<vector>
      using namespace std;
      
      
      //线程的入口函数
      void test(int num) {
          cout << "线程开始执行,线程编号=" << num << endl;
          for (int i = 0; i < 10; ++i);
          cout << "线程执行结束了,线程编号=" << num << endl;
      }
      
      int main() {
          cout << "主线程开始执行" << endl;
          //创建和等待多个线程
          vector<thread> threads;
          //创建10个线程,线程入口统一使用test
          for (int i = 0; i < 10; ++i) {
              threads.push_back(thread(test, i));//创建10个线程,同时这10个线程开始执行
          }
          for (auto i = threads.begin(); i != threads.end(); ++i) {
              i->join();//等待10个线程返回
          }
          cout << "主线程执行结束" << endl;
          return 0;
      }

      在这里插入图片描述

    表明线程的执行是无序的,和操作系统内部对线程的调度有关

    • 数据共享问题分析——mutex

      1. 只读的数据

        #include<iostream>
        #include<thread>
        #include<vector>
        using namespace std;
        
        int global = 10;//共享数据
        
        //线程的入口函数
        void test(int num) {
            cout << "编号为" << std::this_thread::get_id() << "打印global_v的值" << global << endl;
            for (int i = 0; i < 10; ++i);
            cout << "线程执行结束了,线程编号=" << std::this_thread::get_id() << endl;
        }
        
        int main() {
            cout << "主线程开始执行" << endl;
            //创建和等待多个线程
            vector<thread> threads;
            //创建10个线程,线程入口统一使用test
            for (int i = 0; i < 10; ++i) {
                threads.push_back(thread(test, i));//创建10个线程,同时这10个线程开始执行
            }
            for (auto i = threads.begin(); i != threads.end(); ++i) {
                i->join();//等待10个线程返回
            }
            cout << "主线程执行结束" << endl;
            return 0;
        }

        在这里插入图片描述

     所有线程读取的数据都相等,即:**只读数据安全稳定**
    
    1. 有读有写

      #include<iostream>
      #include<thread>
      using namespace std;
      
      int num[10] = { 0 };
      
      void write() {
          for (int i = 0; i < 10; ++i) {
              num[i] = 1;
              this_thread::sleep_for(std::chrono::milliseconds(1));
          }
      }
      
      void read() {
          for (int i = 1; i < 10; ++i) {
              if (num[i] != num[i - 1]) {
                  cout << "数据不一致" << endl;
              }
          }
      }
      
      int main() {
          cout << "主线程开始执行" << endl;
          //创建2个线程,一个线程负责读,一个负责写
          thread write(write);
          thread read(read);
          write.join();
          read.join();
          cout << "主线程执行结束" << endl;
          return 0;
      }

      在这里插入图片描述

    • 共享数据的保护

      1. 互斥量的基本概念

        类的对象,可以理解为一把锁,多个线程尝试用lock()成员函数来加锁这把锁头,只有一个线程可以锁定成功(成功的标志是lock()函数返回),如果没有锁定成功,线程就会阻塞在这里,不断尝试加锁

      2. 互斥量的用法

        1. 先lock(),操作共享数据,unlock()
        2. lock()和unlock()要成对使用,lock()必然要有unlock,每调用一次lock(),必然要调用一次unlock()
        3. 注意:mutex是不可复制对象,所以mutex作为类的成员要注意。C++中thread调用“带mutex的类”的成员函数报错C2661:std::tuple解决方法_tomwillow的博客-CSDN博客
        #include<iostream>
        #include<thread>
        #include<mutex>
        using namespace std;
        
        mutex g_mutex;
        int num[10] = { 0 };
        
        void write() {
            int copy[10];
            for (int i = 0; i < 10; ++i) {
                copy[i] = 1;
                this_thread::sleep_for(std::chrono::milliseconds(1));
            }
            g_mutex.lock();
            memcpy(num, copy, 10);
            g_mutex.unlock();
        }
        
        void read() {
            int copy[10] = { 0 };
            g_mutex.lock();
            memcpy(copy, num, 10);
            g_mutex.unlock();
            for (int i = 1; i < 10; ++i) {
                if (copy[i] != copy[i - 1]) {
                    cout << "数据不一致" << endl;
                }
            }
            
        }
        
        int main() {
            cout << "主线程开始执行" << endl;
            //创建2个线程,一个线程负责读,一个负责写
            thread write(write);
            thread read(read);
            write.join();
            read.join();
            cout << "主线程执行结束" << endl;
            return 0;
        }
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/2020101311253696.png#pic_center)
    
     4. 为了防止忘记unlock(),引入一个叫**std::lock_guard**的类模板。类似于智能指针
    
        (1)std::lock_guard直接取代了lock()和unlock()
    
        (2)lock_guard构造函数中执行了lock(),析构函数中执行了unlock()
    
        ```c++
        #include<iostream>
        #include<thread>
        #include<mutex>
        using namespace std;
        
        mutex g_mutex;
        int num[10] = { 0 };
        
        void write() {
            int copy[10];
            for (int i = 0; i < 10; ++i) {
                copy[i] = 1;
                this_thread::sleep_for(std::chrono::milliseconds(1));
            }
            std::lock_guard<mutex> write_mutex(g_mutex);
            memcpy(num, copy, 10);
        }
        
        void read() {
            int copy[10] = { 0 };
            std::lock_guard<mutex> read_mutex(g_mutex);
            memcpy(copy, num, 10);
            for (int i = 1; i < 10; ++i) {
                if (copy[i] != copy[i - 1]) {
                    cout << "数据不一致" << endl;
                }
            }
            
        }
        
        int main() {
            cout << "主线程开始执行" << endl;
            //创建2个线程,一个线程负责读,一个负责写
            thread write(write);
            thread read(read);
            write.join();
            read.join();
            cout << "主线程执行结束" << endl;
            return 0;
        }
        ```
    
        如果不想线程执行完毕后std::lock_guard才执行析构函数,可以将其和需要加锁的共享数据放到{}中
    
        ```c++
        {
            std::lock_guard<mutex> write_mutex(g_mutex);
            memcpy(num, copy, 10);
        }
        //此时std::lock_guard属于{}这个作用域,当在{}外后,std::lock_guard生命周期结束,就会析构,即执行unlock
        ```
    
    1. 死锁

      1. 死锁的前提是至少有两个互斥量(锁头)才能产生
      2. 目前有两个互斥量,即:锁1,锁2,两个线程A,B

        (1)线程A执行的时候,这个线程先锁锁1,把锁1锁成功了,然后它去锁锁2,此时出现上下文切换,线程A让出CPU

        (2)线程B执行了,这个线程先锁锁2,因为锁2没有被锁,所以锁2锁成功,然后它去锁锁1

        (3)此时死锁产生,线程A拿不到锁2,解不开锁1,线程B拿不到锁1,解不开锁2,两个线程就无限互相等待下去

      3. 死锁的一般解决办法:保证两个互斥量的lock()顺序一致
      4. std::lock()函数模板

        1. 一次锁住两个或两个以上的互斥量(1个不行),它就不会存在多个线程因为锁的顺序导致的死锁风险。
        2. 如果互斥量中有一个没锁住,它就会一直等待,直到所有的互斥量都锁住
        3. 要么多个互斥量都锁住,要么都没有锁住。如果只锁一个其他没成功,它就会立即把已经lock()的都unlock()

          mutex g_mutex1;
          mutex g_mutex2;
          std::lock(g_mutex1,g_mutex2);
          g_mutex1.unlock();
          g_mutex2.unlock();
        4. std::lock_guard的std::adopt_lock

          //是一个结构体对象,其一个标记作用,表示互斥量已经lock()
          //std::adopt_lock让lock_guard不执行lock()
          mutex g_mutex1;
          mutex g_mutex2;
          std::lock(g_mutex1,g_mutex2);
          std::lock_guard<mutex> guard1(g_mutex1,std::adopt_lock);
          std::lock_guard<mutex> guard2(g_mutex2,std::adopt_lock);
      5. unique_lock

        (1)unique_lock是个类模板,和lock_guard类似,都是对mutex进行lock()和unlock()操作的

        (2)unique_lock比lock_guard更灵活,但效率上差一点,内存占用多一点。

        (3)用法和lock_guard类似

        mutex g_mutex;
        std::unique_lock<std::mutex> uniqueLock(g_mutex);

        (4)unique_lock第二个参数

        ​ a. std::adopt_lock(lock_guard也可使用):表示互斥量已经lock(),即:不需要在unique_lock的构造函数进行lock()

        ​ b. std::try_to_lock:会尝试mutex的lock()去锁定mutex,但如果没有锁定成功,就会立即返回,并不会阻塞(前提是使用try_to_lock前不能单独使用lock())

        std::unique_lock<mutex> uniqueLock(g_mutex,std::try_to_lock);
        if(uniqueLock.owns_lock()){//尝试Lock()成功
            
        }
        
        
        //实例
        #include<iostream>
        #include<thread>
        #include<mutex>
        using namespace std;
        
        mutex g_mutex;
        int num[10] = { 0 };
        
        void write() {
            int copy[10];
            for (int i = 0; i < 10; ++i) {
                copy[i] = 1;
            }
            unique_lock<mutex> uniqueLock(g_mutex);
            this_thread::sleep_for(chrono::milliseconds(2000));
            memcpy(num, copy, 10);
        }
        
        void read() {
            int copy[10] = { 0 };
            unique_lock<mutex> uniqueLock(g_mutex,std::try_to_lock);
            if (uniqueLock.owns_lock()) {
                memcpy(copy, num, 10);
            }
            else {
                cout << "线程2没有拿到锁" << endl;
            }
            for (int i = 1; i < 10; ++i) {
                if (copy[i] != copy[i - 1]) {
                    cout << "数据不一致" << endl;
                }
            }
        }
        
        int main() {
            cout << "主线程开始执行" << endl;
            //创建2个线程,一个线程负责读,一个负责写
            thread write(write);
            thread read(read);
            write.join();
            read.join();
            cout << "主线程执行结束" << endl;
            return 0;
        }

        在这里插入图片描述

        ​        c. **std::defer_lock**:并没有给mutex,即:**初始化一个没有lock()的mutex**(defer_lock的前提是不能先lock()),经常配合unique_lock的成员函数使用
    
     6. unique_lock的重要成员函数
    
        (1)lock()
    
        (2)unlock():unique_lock也可以自动解锁
    
        (3)try_lock():(类似于try_to_lock)尝试给互斥量加锁,如果拿不到锁,则返回false,如果拿到了锁,返回true,这个函数是**不阻塞**的
    
        ```c++
        #include<iostream>
        #include<thread>
        #include<mutex>
        using namespace std;
        
        mutex g_mutex;
        int num[10] = { 0 };
        
        void write() {
            int copy[10];
            for (int i = 0; i < 10; ++i) {
                copy[i] = 1;
            }
            unique_lock<mutex> uniqueLock(g_mutex);
            this_thread::sleep_for(chrono::milliseconds(2));
            memcpy(num, copy, 10);
        }
        
        void read() {
            int copy[10] = { 0 };
            unique_lock<mutex> uniqueLock(g_mutex,defer_lock);
            if (uniqueLock.try_lock() == true) {
                memcpy(copy, num, 10);
            }
            else {
                cout << "线程2没有拿到锁" << endl;
            }
            for (int i = 1; i < 10; ++i) {
                if (copy[i] != copy[i - 1]) {
                    cout << "数据不一致" << endl;
                }
            }
        }
        
        int main() {
            cout << "主线程开始执行" << endl;
            //创建2个线程,一个线程负责读,一个负责写
            thread write(write);
            thread read(read);
            write.join();
            read.join();
            cout << "主线程执行结束" << endl;
            return 0;
        }
        ```
    
        ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201013112643439.png#pic_center)
    
    
        (4)release():返回它所管理的mutex对象指针,并**释放所有权**,也就是说,这个unique_lock和mutex不再联系(unlock不会释放所有权)。
    
        ```c++
        #include<iostream>
        #include<thread>
        #include<mutex>
        using namespace std;
        
        mutex g_mutex;
        int num[10] = { 0 };
        
        void write() {
            int copy[10];
            for (int i = 0; i < 10; ++i) {
                copy[i] = 1;
            }
            unique_lock<mutex> uniqueLock(g_mutex);
            mutex* ptx = uniqueLock.release();//释放mutex所有权,所以需要自己unlock()
            memcpy(num, copy, 10);
            ptx->unlock();
        }
        
        void read() {
            int copy[10] = { 0 };
            unique_lock<mutex> uniqueLock(g_mutex);
            memcpy(copy, num, 10);
            for (int i = 1; i < 10; ++i) {
                if (copy[i] != copy[i - 1]) {
                    cout << "数据不一致" << endl;
                }
            }
        }
        
        int main() {
            cout << "主线程开始执行" << endl;
            //创建2个线程,一个线程负责读,一个负责写
            thread write(write);
            thread read(read);
            write.join();
            read.join();
            cout << "主线程执行结束" << endl;
            return 0;
        }
        ```
    
     7. unique_lock所有权
    
        ```c++
        mutex g_mutex;
        unique_lock<mutex> uniqueLock(g_mutex);//uniqueLock拥有g_mutex的所有权
        //不能复制所有权,但是所有权可以转移
        
        //转移方法1
        unique_lock<mutex> uniqueLock_(move(uniqueLock));//当前uniqueLock失去g_mutex控制权,指向空,uniqueLock_指向g_mutex
        
        //转移方法2
        //从函数返回一个局部的unique_lock对象是允许的
        //返回这种局部对象会导致系统生成临时的unique_lock对象,并调用unique_lock的移动构造函数
        unique_lock<mutex> rtn_unique_lock(){
            unique_lock<mutex> uniqueLock(g_mutex);
            return uniqueLock;
        }
        ```
    
    • 单例设计模式共享数据分析、解决,call_once

      1. 单例设计模式

        (1)单例:整个项目中,有某个或者某些特殊的类,并且只能创建一个属于该类的对象

        (2)单例类示例

        #include<iostream>
        using namespace std;
        
        class MyCAS {//这是一个单例类
        private:
            MyCAS(){}//私有化构造函数
        private:
            static MyCAS* m_instance;//静态成员变量
        public:
            static MyCAS* getInstance() {
                if (m_instance == nullptr) {
                    m_instance = new MyCAS();
                    static Release release;//当程序退出时执行析构函数
                }
                return m_instance;
            }
            class Release {//用来释放对象
            public:
                ~Release() {
                    if (MyCAS::m_instance!=nullptr) {
                        delete m_instance;
                        MyCAS::m_instance = nullptr;
                    }
                }
            };
        };
        //类静态变量初始化
        MyCAS* MyCAS::m_instance = nullptr;
        
        int main() {
            MyCAS* ptr = MyCAS::getInstance();//创建一个对象,返回该类(对象)指针
            MyCAS* ptr_ = MyCAS::getInstance();
            cout << "ptr=" << ptr << endl;
            cout << "ptr_=" << ptr_ << endl;
            return 0;
        }

        在这里插入图片描述

      2. 单例设计模式共享数据问题分析、解决

        问题:在创建的线程(非主线程)中创建单例类对象,并且这种线程可能不止一个,就需要将getInstance()这种成员函数互斥

        原因:当第一个线程在new之前失去cpu,第二个线程就会执行new一次,此时第一个线程再次执行,就又会new一次,这就不符合单例设计模式了

        示例:

        #include<iostream>
        #include<thread>
        #include<mutex>
        using namespace std;
        
        mutex g_mutex;
        
        class MyCAS {//这是一个单例类
        private:
            MyCAS(){}//私有化构造函数
        private:
            static MyCAS* m_instance;//静态成员变量
        public:
            static MyCAS* getInstance() {
                //提高效率,只有第一次没初始化时会加锁
                if (m_instance == nullptr) {//双重锁定(双重检查)
                    unique_lock<mutex> lock(g_mutex);//自动加锁
                    if (m_instance == nullptr) {
                        m_instance = new MyCAS();
                        static Release release;//当程序退出时执行析构函数
                    }
                }
                return m_instance;
            }
        
            class Release {//用来释放对象
            public:
                ~Release() {
                    if (MyCAS::m_instance!=nullptr) {
                        delete m_instance;
                        MyCAS::m_instance = nullptr;
                    }
                }
            };
        
            void fun() {
                cout << "测试" << endl;
            }
        };
        //类静态变量初始化
        MyCAS* MyCAS::m_instance = nullptr;
        
        void thread1() {
            cout << "线程th1开始执行" << endl;
            MyCAS* ptr = MyCAS::getInstance();
            cout << "线程th1执行完毕" << endl;
        }
        
        void thread2() {
            cout << "线程th2开始执行" << endl;
            MyCAS* ptr = MyCAS::getInstance();
            cout << "线程th2执行完毕" << endl;
        }
        
        int main() {
            thread th1(thread1);
            thread th2(thread2);
            th1.join();
            th2.join();
            return 0;
        }
      3. std::call_once()——c++11标准

        函数的第二个参数是一个函数名,需要和std::once_flag标记结合使用

        功能:保证函数只被调用一次,即:通过std::once_flag这个标记来判断函数是否执行,当调用call_once()成功后,call_once()就把这个标记设置为一种已调用状态。后续再次调用call_once(),只要once_falg被设置为已调用,就不会执行了

        具备互斥量的能力

        #include<iostream>
        #include<thread>
        #include <mutex>
        using namespace std;
        
        once_flag g_flag;//这是系统定义的标记
        
        class MyCAS {//这是一个单例类
            static void CreateInstance() {//只被调用一次
                m_instance = new MyCAS();
                static Release release;//当程序退出时执行析构函数
            }
        private:
            MyCAS(){}//私有化构造函数
        private:
            static MyCAS* m_instance;//静态成员变量
        public:
            static MyCAS* getInstance() {
                //两个线程同时执行到这里,其中一个线程要等另外一个线程执行完毕CreateInstance后,就会放弃执行CreateInstance
                call_once(g_flag, CreateInstance);
                return m_instance;
            }
        
            class Release {//用来释放对象
            public:
                ~Release() {
                    if (MyCAS::m_instance!=nullptr) {
                        delete m_instance;
                        MyCAS::m_instance = nullptr;
                    }
                }
            };
        
            void fun() {
                cout << "测试" << endl;
            }
        };
        //类静态变量初始化
        MyCAS* MyCAS::m_instance = nullptr;
        
        void thread1() {
            cout << "线程th1开始执行" << endl;
            MyCAS* ptr = MyCAS::getInstance();
            cout << "线程th1执行完毕" << endl;
        }
        
        void thread2() {
            cout << "线程th2开始执行" << endl;
            MyCAS* ptr = MyCAS::getInstance();
            cout << "线程th2执行完毕" << endl;
        }
        
        int main() {
            thread th1(thread1);
            thread th2(thread2);
            th1.join();
            th2.join();
            return 0;
        }

        # c++11线程的条件变量

    • 轮询机制:每隔一定时间,进行查询

      缺点:查询不能太频繁(浪费CPU),也不能太不频繁(缓冲区满),难以把握。性能不佳

      #include <iostream>
      #include<thread>
      #include<mutex>
      using namespace std;
      
      mutex g_mutex;
      int g_buf[100];//缓冲区,最多存放一百个数
      int g_count = 0;
      
      //第一个线程:生产者
      void Producer() {
          while (true) {
              int r = rand() % 20 + 1;//生成一个1……20之间的随机数
              this_thread::sleep_for(chrono::milliseconds(50 * r));//休息时间在50-1000毫秒之间
              //存放一个物品(这里存放的数据代表物品)
              g_mutex.lock();
              g_buf[g_count] = r;
              ++g_count;
              cout << "放入物品:" << r << endl;
              g_mutex.unlock();
          }
      }
      
      //第二个线程:消费者
      void Consume() {
          while (true) {
              this_thread::sleep_for(chrono::milliseconds(50));
              g_mutex.lock();
              if (g_count > 0) {
                  for (int i = 0; i < g_count; ++i) {
                      cout << "消耗物品:" << g_buf[i] << endl;
                  }
                  g_count = 0;
              }
              g_mutex.unlock();
          }
      }
      
      int main()
      {
          srand(time(nullptr));
          thread producer(Producer);
          thread consume(Consume);
          producer.join();
          consume.join();
          return 0;
      }

      在这里插入图片描述

    • 条件变量——std::condition_variable

      (1)std::condition_variable实际是一个类,是一个和条件相关的一个类,即:等待一个条件达成。

      (2)这个类是需要和互斥量来配合工作,用的时候我们要生成这个类的对象

      (3)wait()

      ​ a. 如果第二个参数(可调用对象)返回的值是false,那么wait()将解锁互斥量,并堵塞到 本行。堵塞到其他某个线程调用notify_one()成员函数为止。如果第二个参数返回值是true,那么wait()会直接返回

      ​ b. 如果wait()没有第二个参数,那么就和第二个参数返回false效果一样。

      (4)notify_one()

      ​ (1)尝试把一个wait()的线程唤醒,如果没有wait()线程,那么notify_one()就没效果,但是不会阻塞在notify_one()这里

      ​ (2)当其他线程用notify_one()将wait()【原本阻塞】,wait()就开始恢复干活了,即

      ​ a. 不断尝试重新获取互斥量锁,如果获取不到,线程就阻塞在wait()这里等着获取锁。 b. 如果获取到就继续执行;获取到锁就执行lock()。

      ​ 如果wait有第二个参数,就判断这个参数返回值,如果返回值为false,wait()又会对互斥量解锁,并再次阻塞,等待notify_one()唤醒

      ​ 如果wait没有第二个参数,则线程继续执行

      (5)示例

      #include <iostream>
      #include<thread>
      #include<mutex>
      using namespace std;
      
      mutex g_mutex;
      condition_variable g_cond;//生成一个条件变量对象
      int g_buf[100];//缓冲区,最多存放一百个数
      int g_count = 0;
      
      //第一个线程:生产者
      void Producer() {
          while (true) {
              int r = rand() % 20 + 1;//生成一个1……20之间的随机数
              this_thread::sleep_for(chrono::milliseconds(50 * r));//避免本线程notify_one()后比wait()先拿到锁,休息时间在50-1000毫秒之间
              //存放一个物品(这里存放的数据代表物品)
              unique_lock<mutex> lock(g_mutex);
              g_buf[g_count] = r;
              ++g_count;
              cout << "放入物品:" << r << endl;
              g_cond.notify_one();
          }
      }
      
      //第二个线程:消费者
      void Consume() {
          while (true) {
              unique_lock<mutex> lock(g_mutex);
              g_cond.wait(lock, [&] {//lambda为可调用对象
                  if (g_count > 0)
                      return true;
                  return false;
                  });
              for (int i = 0; i < g_count; ++i) {
                  cout << "消耗物品:" << g_buf[i] << endl;
              }
              g_count = 0;
          }
      }
      
      int main()
      {
          srand(time(nullptr));
          thread producer(Producer);
          thread consume(Consume);
          producer.join();
          consume.join();
          return 0;
      }
    • 上述代码深入思考

      (1) 当删除this_thread::sleep_for(chrono::milliseconds(50 * r))后,放入和消耗就可能不会交替执行,因为notify_one()后,线程可能先于wait()拿到锁

      在这里插入图片描述

    (2)注意:notify_one()不一定起作用,但不会阻塞。因为执行notify_one()时,如果没有其他线程wait(),它就没有效果

    • notify_all()

      当程序中有多个线程使用wait()时,使用notify_one()只会在某时刻唤醒其中任意一个线程,另外其他线程依然在阻塞中,即:任意时刻只有一个wait()尝试拿锁,其他都在阻塞中

      使用notify_all()会将所有wait()的线程都唤醒,所有线程的wait()都会尝试拿锁

    c++11 async、future、packaged_task、promise

    • std::asyc、std::future创建后台任务并返回值

      1. std::asyc:是一个函数模板,用来启动一个异步任务,启动一个异步任务后,它会返回一个std::future(类模板)对象
      2. 启动一个异步任务:就是自动创建一个线程并开始执行对应的线程入口函数,它返回一个std::future对象
      3. std::future对象里就含有线程入口函数返回的结果(线程返回的结果),可以通过调用future对象的成员函数get()来获取结果
      4. std::future:提供了一种访问异步操作结果的机制,即:这个结果可能无法马上到达,但是不久的将来,当线程执行完毕的时候,就可以拿到结果
      5. 示例

        #include<iostream>
        #include<future>
        using namespace std;
        
        int myThread() {//线程入口函数
            cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
            this_thread::sleep_for(chrono::milliseconds(5000));
            cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
            return 5;
        }
        
        int main() {
            cout << "main() start," << "threadid=" << this_thread::get_id() << endl;
            future<int> result = async(myThread);//创建一个线程并执行,但是主函数不会阻塞在这里,会继续向下执行
            cout << "continue……!" << endl;
            cout << result.get() << endl;//主函数会阻塞在这里,等待线程返回
            cout << "main() end," << "threadid=" << this_thread::get_id() << endl;
            return 0;
        }
        #include<iostream>
        #include<future>
        using namespace std;
        
        class Thread {
        public:
            int myThread(int num) {//线程入口函数
                cout << num << endl;
                cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
                this_thread::sleep_for(chrono::milliseconds(5000));
                cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
                return 5;
            }
        };
        
        
        int main() {
            cout << "main() start," << "threadid=" << this_thread::get_id() << endl;
            Thread th;
            future<int> result = async(&Thread::myThread,&th,12);//创建一个线程并执行,但是主函数不会阻塞在这里,会继续向下执行
            cout << "continue……!" << endl;
            cout << result.get() << endl;//主函数会阻塞在这里,等待线程返回
            cout << "main() end," << "threadid=" << this_thread::get_id() << endl;
            return 0;
        }
      6. 上述程序通过future对象的get()成员函数等待线程结束并返回结果,即:会阻塞future对象所在的线程,并返回结果
      7. future对象的get()成员函数只能调用一次
      8. future对象还有一个wait()成员函数,该函数只是等待线程结束,本身不会返回结果
      9. 额外向std::async()传递一个参数,该参数类型是std::launch类型(枚举类型),来达到一些特殊的目的

        (1)std::launch::deferred:不会创建新线程,通过future对象调用get()或者wait()函数,就会直接调用async中的可调用对象

        #include<iostream>
        #include<future>
        using namespace std;
        
        class Thread {
        public:
            int myThread(int num) {//线程入口函数
                cout << num << endl;
                cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
                this_thread::sleep_for(chrono::milliseconds(5000));
                cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
                return 5;
            }
        };
        
        
        int main() {
            cout << "main() start," << "threadid=" << this_thread::get_id() << endl;
            Thread th;
            future<int> result = async(launch::deferred, &Thread::myThread, &th, 12);
            cout << "continue……!" << endl;
            cout << result.get() << endl;
            cout << "main() end," << "threadid=" << this_thread::get_id() << endl;
            return 0;
        }

        在这里插入图片描述

     (2)**std::launch::async**:强制这个异步任务在新线程上执行,即:系统必须创建出新线程执行可调用对象
    
     (3)**std::launch::async | std::launch::deferred**:这是**async的默认值**,表示调用async的行为可能是创建新线程并立即执行,或者没有创建新线程并延迟到调用get()或者wait()才开始执行任务入口函数
    
    1. std::async和std::thread的区别:

      (1)std::async不一定会创建新线程执行可调用对象。std::thread会创建新线程,如果系统资源紧张,创建线程失败,那么整个程序就会崩溃

      (2)std::async调用方法可以用简单的get()函数拿到线程可调用对象的返回值

      (3)std::thread创建的线程过多,可能创建失败,系统报告异常,崩溃。而std::async一般不会报告异常、崩溃,因为当系统资源紧张导致无法创建新线程的时候,std::async使用默认值调用时就不会创建新线程,而是后序当某个线程使用get()获取返回值时,就在该线程执行可调用对象

    2. std::async使用默认值

      当std::async使用默认值作为参数时,会产生不确定性【即:是否创建新线程】

      借助future的wait_for()来判断是否创建新线程

      #include<iostream>
      #include<thread>
      #include<atomic>
      #include<future>
      using namespace std;
      
      atomic<int> g_num = 0;
      
      int myThread() {
          cout << "myThread() start, " << "thread_id=" << this_thread::get_id() << endl;
          cout << "myThread() end, " << "thread_id=" << this_thread::get_id() << endl;
          return 1;
      }
      
      int main() {
          cout << "main() start, " << "thread_id=" << this_thread::get_id() << endl;
          future<int> result = async(myThread);
          future_status status = result.wait_for(0s);//等待chrono::seconds(0)
          if (status == future_status::deferred) {
              cout << "没有创建新线程" << endl;
              cout << result.get() << endl;//此时才执行myThread()
          }
          else {
              //创建了新线程
              if (status == future_status::ready) {
                  cout << "线程成功创建" << endl;
                  cout << result.get() << endl;
              }
              else if (status == future_status::timeout) {
                  //超时,线程还没执行完毕
                  cout << "超时,线程还在执行中" << endl;
                  cout << result.get() << endl;
              }
          }
          cout << "main() end, " << "thread_id=" << this_thread::get_id() << endl;
          return 0;
      }
    • std::packaged_task

      1. std::packaged_task:是一个类模板,模板参数是各种可调用对象,通过std::packaged_task把各种可调用对象包装起来,方便将来作为线程入口函数
      2. packaged_task:包装的对象还是可以直接调用的
      3. 可以通过get_future()获取future对象,从而取得线程的返回值
      4. 示例

        #include<iostream>
        #include <future>
        #include<thread>
        using namespace std;
        
        int myThread(int num) {//线程入口函数
            cout << num << endl;
            cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
            this_thread::sleep_for(chrono::milliseconds(5000));
            cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
            return 5;
        }
        
        int main() {
            cout << "main() start," << "threadid=" << this_thread::get_id() << endl;
            packaged_task<int(int)> myFunc(myThread);//将函数myThread通过packaged_task包装起来
            thread th(std::ref(myFunc), 1);
            th.join();
            std::future<int> result = myFunc.get_future();
            cout << result.get() << endl;
            cout << "main() end," << "threadid=" << this_thread::get_id() << endl;
            return 0;
        }
        #include<iostream>
        #include <future>
        #include<thread>
        using namespace std;
        
        int main() {
            cout << "main() start," << "threadid=" << this_thread::get_id() << endl;
            packaged_task<int(int)> myFunc([](int num) {
                    cout << num << endl;
                    cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
                    this_thread::sleep_for(chrono::milliseconds(5000));
                    cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
                    return 5;
                });
            thread th(ref(myFunc), 12);
            th.join();
            cout << "main() end," << "threadid=" << this_thread::get_id() << endl;
            return 0;
        }
        #include<iostream>
        #include <future>
        #include<thread>
        using namespace std;
        
        int main() {
            cout << "main() start," << "threadid=" << this_thread::get_id() << endl;
            packaged_task<int(int)> myFunc([](int num) {
                    cout << num << endl;
                    cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
                    this_thread::sleep_for(chrono::milliseconds(5000));
                    cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
                    return 5;
                });
            myFunc(12);//直接调用
            future<int> result = myFunc.get_future();
            cout << result.get() << endl;//注意,没有创建新线程,而是触发lambda表达式执行,即:相当于函数调用
            cout << "main() end," << "threadid=" << this_thread::get_id() << endl;
            return 0;
        }
        #include<iostream>
        #include <future>
        #include<thread>
        #include<vector>
        using namespace std;
        
        vector<packaged_task<int(int)>> vec;
        
        int main() {
            cout << "main() start," << "threadid=" << this_thread::get_id() << endl;
            packaged_task<int(int)> myFunc([](int num) {
                cout << num << endl;
                cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
                this_thread::sleep_for(chrono::milliseconds(5000));
                cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
                return 5;
                });
            vec.push_back(move(myFunc));//这里用到了移动语义,此时myFunc为空
            packaged_task<int(int)> myFunc_ = move(vec.back());
            vec.pop_back();
            myFunc_(123);
            future<int> result = myFunc_.get_future();
            cout << result.get() << endl;
            cout << "main() end," << "threadid=" << this_thread::get_id() << endl;
            return 0;
        }
    • std::promise

      std::promise也是一个类模板,能够在某个线程中给它赋值,然后可以在其他线程中,将这个值取出使用

      #include<iostream>
      #include<thread>
      #include <future>
      using namespace std;
      
      void myThread(promise<int>& temp, int num) {
          //模拟完成一系列复杂操作
          this_thread::sleep_for(chrono::milliseconds(5000));
          temp.set_value(num);//将结果保存到promise对象中
      }
      
      int main() {
          promise<int> myTemp;//声明一个promise对象,保存类型为int
          thread th(myThread, ref(myTemp), 520);
          th.join();
          //获取结果值
          future<int> result = myTemp.get_future();//promise和future绑定,用于获取线程返回值
          cout << result.get() << endl;
          return 0;
      }
      #include<iostream>
      #include<thread>
      #include <future>
      using namespace std;
      
      void myThread(promise<int>& temp, int num) {
          //模拟完成一系列复杂操作
          this_thread::sleep_for(chrono::milliseconds(5000));
          temp.set_value(num);//将结果保存到promise对象中
      }
      
      void myThread_(future<int>& temp) {
          int result = temp.get();
          cout << "myThread_ result=" << result << endl;
      }
      
      int main() {
          promise<int> myTemp;//声明一个promise对象,保存类型为int
          thread th(myThread, ref(myTemp), 520);
          th.join();
          future<int> result = myTemp.get_future();//promise和future绑定,用于获取线程返回值
          thread th_(myThread_, ref(result));
          th_.join();
          return 0;
      }

    future其他成员函数、shared_future、atomic

    • std::future其它成员函数

      wait_for(time):阻塞time时长,若time时长后,线程还没返回,则wait_for返回future_status::timeout。如果在time时长之内,线程成功返回,则wait_for()返回future_status::ready。如果async第一个参数设置为std::launch::deferred,则wait_for()返回future_status::deferred

      #include<iostream>
      #include<future>
      using namespace std;
      
      int myThread() {//线程入口函数
          cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
          this_thread::sleep_for(chrono::milliseconds(1000));
          cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
          return 5;
      }
      
      int main() {
          cout << "main() start," << "threadid=" << this_thread::get_id() << endl;
          future<int> result = async(myThread);//创建一个线程并执行,但是主函数不会阻塞在这里,会继续向下执行
          cout << "continue……!" << endl;
          //枚举类型
          future_status status = result.wait_for(chrono::seconds(2));
          if (status == future_status::timeout) {
              cout << "timeout" << endl;
          }
          else if (status == future_status::ready) {
              cout << "成功返回" << endl;
              cout << result.get() << endl;
          }
          else if (status == future_status::deferred) {
              cout << "deferred" << endl;
              cout << result.get() << endl;
          }
          cout << "main() end," << "threadid=" << this_thread::get_id() << endl;
          return 0;
      }
      

      在这里插入图片描述

    • std::shared_future

      std::future只能调用一次get()函数,因为get()函数的设计是一个移动语义

      std::shared_future:也是一个类模板,get()函数是复制数据

      #include<iostream>
      #include <future>
      #include<thread>
      using namespace std;
      
      int myThread() {//线程入口函数
          cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
          this_thread::sleep_for(chrono::milliseconds(5000));
          cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
          return 5;
      }
      
      void myThread_(shared_future<int>& temp) {
          cout << "myThread_() start," << "threadid=" << this_thread::get_id() << endl;
          cout << "myThread_():" << temp.get() << endl;
          cout << "myThread_() end," << "threadid=" << this_thread::get_id() << endl;
      }
      
      int main() {
          cout << "main() start," << "threadid=" << this_thread::get_id() << endl;
          packaged_task<int(void)> myFunc(myThread);//将函数myThread通过packaged_task包装起来
          thread th(std::ref(myFunc));
          th.join();
          shared_future<int> result = myFunc.get_future();
          cout << "main():"<< result.get() << endl;
          thread th_(myThread_, std::ref(result));
          th_.join();
          cout << "main() end," << "threadid=" << this_thread::get_id() << endl;
          return 0;
      }

      在这里插入图片描述

    • std::atomic——原子操作

      原子操作概念及其范例

      (1)原子操作:是指“不可分割的操作”,要么执行成功,要么失败,即:是在多线程中不会打断的程序执行片段

      (2)原子操作:比互斥量的效率更胜一筹

      (3)互斥量加锁一般针对的是一个代码片段,而原子操作针对的一般都是一个变量,而不是一个代码片段

      (4)std::atomic:类模板,用来代表原子操作

      (5)示例

      #include<iostream>
      #include<thread>
      #include<atomic>
      using namespace std;
      
      atomic<int> g_num = 0;
      
      void Write() {
          for (int i = 0; i < 1000000; ++i) {
              //可替换成g_num+=1;
              ++g_num;//操作为原子操作,在多线程中不会被打断
          }
      }
      
      int main() {
          thread th1(Write);
          thread th2(Write);
          th1.join();
          th2.join();
          cout<<"两个线程执行完毕,最终g_num="<<g_num<<endl;
          return 0;
      }
      #include<iostream>
      #include<thread>
      #include<atomic>
      using namespace std;
      
      atomic<bool> g_flag = false;//线程退出标记
      
      void myThread() {
          cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
          while (!g_flag) {
              cout << "myThread() run," << "threadid=" << this_thread::get_id() << endl;
              this_thread::sleep_for(chrono::milliseconds(1000));
          }
          cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
      }
      
      int main() {
          thread th1(myThread);
          thread th2(myThread);
          this_thread::sleep_for(chrono::milliseconds(2000));
          g_flag = true;
          th1.join();
          th2.join();
          return 0;
      }

      (6)atomic原子操作,针对++,--,+=,-=,*=,&=,!=是支持的,其它可能不支持

      #include<iostream>
      #include<thread>
      #include<atomic>
      using namespace std;
      
      atomic<int> g_num = 0;
      
      void Write() {
          for (int i = 0; i < 1000000; ++i) {
              g_num = g_num + 1;
          }
      }
      
      int main() {
          thread th1(Write);
          thread th2(Write);
          th1.join();
          th2.join();
          cout << "两个线程执行完毕,最终g_num=" << g_num << endl;
          return 0;
      }

      在这里插入图片描述

      (7)load()函数:以原子方式读取atomic对象的值

      atomic<int> atm1;
      atomic<int> atm2=atm1;//尝试引用已删除的函数,拷贝赋值运算符也不让用
      atomic<int> atm3(atm1.load())

      (8)store()函数:以原子方式写入内容

      atomic<int> atm;
      atm.store(1);

    windows临界区、其他各种mutex互斥量

    • windows临界区

      #include <iostream>
      #include<thread>
      #include<mutex>
      #include<Windows.h>
      using namespace std;
      
      #define WINDOWS
      
      #ifdef WINDOWS
          CRITICAL_SECTION winsec;//windows中的临界区,类似于mutex
      #endif
      
      mutex g_mutex;
      int g_buf[100];//缓冲区,最多存放一百个数
      int g_count = 0;
      
      //第一个线程:生产者
      void Producer() {
          while (true) {
              int r = rand() % 20 + 1;//生成一个1……20之间的随机数
              this_thread::sleep_for(chrono::milliseconds(50 * r));//休息时间在50-1000毫秒之间
              //存放一个物品(这里存放的数据代表物品)
      #ifdef WINDOWS
              EnterCriticalSection(&winsec);//进入临界区,相当于mutex.lock()
              g_buf[g_count] = r;
              ++g_count;
              cout << "放入物品:" << r << endl;
              LeaveCriticalSection(&winsec);//退出临界区,相当于mutex.unlock()
      #else
              g_mutex.lock();
              g_buf[g_count] = r;
              ++g_count;
              cout << "放入物品:" << r << endl;
              g_mutex.unlock();
      #endif // WINDOWS
          }
      }
      
      //第二个线程:消费者
      void Consume() {
          while (true) {
              this_thread::sleep_for(chrono::milliseconds(50));//轮询
      #ifdef WINDOWS
              EnterCriticalSection(&winsec);
              if (g_count > 0) {
                  for (int i = 0; i < g_count; ++i) {
                      cout << "消耗物品:" << g_buf[i] << endl;
                  }
                  g_count = 0;
              }
              LeaveCriticalSection(&winsec);
      #else
              g_mutex.lock();
              if (g_count > 0) {
                  for (int i = 0; i < g_count; ++i) {
                      cout << "消耗物品:" << g_buf[i] << endl;
                  }
                  g_count = 0;
              }
              g_mutex.unlock();
      #endif // WINDOWS
      
          }
      }
      
      int main()
      {
      #ifdef WINDOWS
          InitializeCriticalSection(&winsec);//使用临界区之前初始化
      #endif // WINDOWS
      
          srand(time(nullptr));
          thread producer(Producer);
          thread consume(Consume);
          producer.join();
          consume.join();
          return 0;
      }
    • 多次进入临界区试验

      (1)在同一个线程中,windows中相同临界区变量代表的临界区的进入(EnterCriticalSection)可以被多次调用,但是EnterCriticalSection的调用次数需要和LeaveCriticalSection的调用次数要相等(否则,该线程始终都在临界区中,该线程一直执行,另一个线程阻塞)

      (2)c++11中mutex不允许lock多次

    • 自动析构技术

      #include <iostream>
      #include<thread>
      #include<mutex>
      #include<Windows.h>
      using namespace std;
      
      #define WINDOWS
      
      //用于自动释放windows下的临界区,防止忘记LeaveCriticalSection
      //类似与std::lock_guard
      //RAII类(Resource Acquisition initialization):资源获取即初始化
      class cWinLock {
      public:
          cWinLock(CRITICAL_SECTION* ptr) :ptr(ptr) {
              EnterCriticalSection(ptr);
          }
          ~cWinLock() {
              LeaveCriticalSection(ptr);
          }
      private:
          CRITICAL_SECTION* ptr;
      };
      
      #ifdef WINDOWS
          CRITICAL_SECTION winsec;//windows中的临界区,类似于mutex
      #endif
      
      mutex g_mutex;
      int g_buf[100];//缓冲区,最多存放一百个数
      int g_count = 0;
      
      //第一个线程:生产者
      void Producer() {
          while (true) {
              int r = rand() % 20 + 1;//生成一个1……20之间的随机数
              this_thread::sleep_for(chrono::milliseconds(50 * r));//休息时间在50-1000毫秒之间
              //存放一个物品(这里存放的数据代表物品)
      #ifdef WINDOWS
              cWinLock wLock(&winsec);
              cWinLock wLock_(&winsec);
              g_buf[g_count] = r;
              ++g_count;
              cout << "放入物品:" << r << endl;
      #else
              lock_guard<mutex> lock(g_mutex);
              g_buf[g_count] = r;
              ++g_count;
              cout << "放入物品:" << r << endl;
      #endif // WINDOWS
          }
      }
      
      //第二个线程:消费者
      void Consume() {
          while (true) {
              this_thread::sleep_for(chrono::milliseconds(50));//轮询
      #ifdef WINDOWS
              cWinLock wLock(&winsec);
              if (g_count > 0) {
                  for (int i = 0; i < g_count; ++i) {
                      cout << "消耗物品:" << g_buf[i] << endl;
                  }
                  g_count = 0;
              }
      #else
              lock_guard<mutex> lock(g_mutex);
              if (g_count > 0) {
                  for (int i = 0; i < g_count; ++i) {
                      cout << "消耗物品:" << g_buf[i] << endl;
                  }
                  g_count = 0;
              }
      #endif // WINDOWS
      
          }
      }
      
      int main()
      {
      #ifdef WINDOWS
          InitializeCriticalSection(&winsec);//使用临界区之前初始化
      #endif // WINDOWS
      
          srand(time(nullptr));
          thread producer(Producer);
          thread consume(Consume);
          producer.join();
          consume.join();
          return 0;
      }
    • recursive_mutex递归的独占互斥量

      std::mutex:独占互斥量,在本线程lock后,本线程无法继续lock【除非unlock后】,其它线程也无法lock

      std::recursive_mutex:递归的独占互斥量,允许同一个线程中同一个互斥量多次被lock【第一次lock后没有unlock】,效率上比mutex低

      #include <iostream>
      #include<thread>
      #include<mutex>
      #include<Windows.h>
      using namespace std;
      
      recursive_mutex g_mutex;
      int g_buf[100];//缓冲区,最多存放一百个数
      int g_count = 0;
      
      //第一个线程:生产者
      void Producer() {
          while (true) {
              int r = rand() % 20 + 1;//生成一个1……20之间的随机数
              this_thread::sleep_for(chrono::milliseconds(50 * r));//休息时间在50-1000毫秒之间
              //存放一个物品(这里存放的数据代表物品)
              lock_guard<recursive_mutex> lock(g_mutex);
              lock_guard<recursive_mutex> lock_(g_mutex);
              g_buf[g_count] = r;
              ++g_count;
              cout << "放入物品:" << r << endl;
          }
      }
      
      //第二个线程:消费者
      void Consume() {
          while (true) {
              this_thread::sleep_for(chrono::milliseconds(50));//轮询
              lock_guard<recursive_mutex> lock(g_mutex);
              if (g_count > 0) {
                  for (int i = 0; i < g_count; ++i) {
                      cout << "消耗物品:" << g_buf[i] << endl;
                  }
                  g_count = 0;
              }
          }
      }
      
      int main()
      {
          srand(time(nullptr));
          thread producer(Producer);
          thread consume(Consume);
          producer.join();
          consume.join();
          return 0;
      }
      
    • 带超时的互斥量std::timed_mutex和std::recursive_timed_mutex

      1. std::timed_mutex:带超时功能的独占互斥量

        (1)try_lock_for():参数是一段时间,等待一段时间,如果等待超时或者lock成功,继续流程

        #include <iostream>
        #include<thread>
        #include<mutex>
        #include<Windows.h>
        using namespace std;
        
        timed_mutex g_mutex;//带超时功能的独占互斥量
        int g_buf[100];//缓冲区,最多存放一百个数
        int g_count = 0;
        
        //第一个线程:生产者
        void Producer() {
            while (true) {
                int r = rand() % 20 + 1;//生成一个1……20之间的随机数
                //存放一个物品(这里存放的数据代表物品)
                if (g_mutex.try_lock_for(10ms)) {//等待100毫秒尝试lock
                    //在100毫秒内lock成功
                    g_buf[g_count] = r;
                    ++g_count;
                    cout << "放入物品:" << r << endl;
                    g_mutex.unlock();
                }
                else {
                    //没有在100毫秒内lock成功
                    cout << "Producer lock失败" << endl;
                    this_thread::sleep_for(chrono::microseconds(100));
                }
            }
        }
        
        //第二个线程:消费者
        void Consume() {
            while (true) {
                this_thread::sleep_for(chrono::milliseconds(50));//轮询
                lock_guard<timed_mutex> lock(g_mutex);
                if (g_count > 0) {
                    for (int i = 0; i < g_count; ++i) {
                        cout << "消耗物品:" << g_buf[i] << endl;
                    }
                    g_count = 0;
                }
            }
        }
        
        int main()
        {
            srand(time(nullptr));
            thread producer(Producer);
            thread consume(Consume);
            producer.join();
            consume.join();
            return 0;
        }

        在这里插入图片描述

     (2)try_lock_until:参数是一个未来时间点,在这个未来时间没到的时间内,不管lock是否成功,流程继续
    
     ```c++
     #include <iostream>
     #include<thread>
     #include<mutex>
     #include<Windows.h>
     using namespace std;
     
     timed_mutex g_mutex;//带超时功能的独占互斥量
     int g_buf[100];//缓冲区,最多存放一百个数
     int g_count = 0;
     
     //第一个线程:生产者
     void Producer() {
         while (true) {
             int r = rand() % 20 + 1;//生成一个1……20之间的随机数
             //存放一个物品(这里存放的数据代表物品)
             if (g_mutex.try_lock_until(chrono::steady_clock::now() + 100ms)) {//当前时间加上100ms
                 //在以当前时刻算起的100ms内,lock成功
                 g_buf[g_count] = r;
                 ++g_count;
                 cout << "放入物品:" << r << endl;
                 g_mutex.unlock();
             }
             else {
                 cout << "Producer lock失败" << endl;
                 this_thread::sleep_for(chrono::microseconds(100));
             }
         }
     }
     
     //第二个线程:消费者
     void Consume() {
         while (true) {
             this_thread::sleep_for(chrono::milliseconds(50));//轮询
             lock_guard<timed_mutex> lock(g_mutex);
             if (g_count > 0) {
                 for (int i = 0; i < g_count; ++i) {
                     cout << "消耗物品:" << g_buf[i] << endl;
                 }
                 g_count = 0;
             }
         }
     }
     
     int main()
     {
         srand(time(nullptr));
         thread producer(Producer);
         thread consume(Consume);
         producer.join();
         consume.join();
         return 0;
     }
     
     ```
    
     
    
    1. std::recursive_timed:带超时功能的递归独占互斥量
    查看原文

    赞 0 收藏 0 评论 0

    dr526 关注了用户 · 2020-05-21

    陈琦 @chen_5ec331606ce75

    资深敏捷测试顾问,作为国内知名项目管理软件——禅道的团队成员,主要负责开源自动化测试管理框架——ZTF的开发工作。拥有十多年的敏捷过程实践经验,现致力于测试自动化和DevOps相关领域的实践和研究。

    关注 271

    dr526 关注了专栏 · 2020-05-21

    前端 1943

    前端技术文章

    关注 2465

    dr526 关注了用户 · 2020-05-21

    编程码农 @onlythinking

    Life is so short, do something to make yourself happy, such as coding.

    关注 544

    认证与成就

    • 获得 1 次点赞
    • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

    擅长技能
    编辑

    开源项目 & 著作
    编辑

    (゚∀゚ )
    暂时没有

    注册于 2020-05-21
    个人主页被 407 人浏览