IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 操作系统并发性(一):线程、原子性 -> 正文阅读

[C++知识库]操作系统并发性(一):线程、原子性

线程及线程与进程的区别

一个线程类似于独立的进程,只有一点区别:它们共享地址空间,从而可以访问相同的内存数据。多个线程可以运行在同一个CPU上,也可以运行在不同的CPU上。

线程是程序员创建的实体,但是被操作系统调度。

为什么有多线程程序

经典观点是一个程序只有一个执行点(一个PC),但这在有些情境下不适用。

比如我们要实现一个定时器功能:当时间达到S时,使程序离开当前PC,执行另一个模块。那么我们就需要给这个定时器创建一个线程。

所谓多线程程序就是该程序可能同时在执行多条指令,这些线程或者运行在不同的CPU上(真正意义的并发),或者通过上下文切换进行时分共享。

线程API

//头文件
#include <pthread.h>    //使用g++编译时需要添加属性 -l pthread
//数据结构(类似于进程列表)
pthread_t p1, p2;
//线程创建:pthread_create
void* mythred(void* arg);
rc = pthread_create(&p1, NULL, mythred, (void*)arg);assert(rc == 0)
rc = pthread_create(&p2, NULL, mythred, (void*)arg);assert(rc == 0);
//等待线程完成
pthread_join(p1, NULL);
pthread_join(p2, NULL);

我们创建线程的目的是为了让它执行特定的代码块(对应C语言的函数),所以在创建进程时,后两个参数指明了函数名与函数参数。
在等待线程完成时,第二个参数指明了函数的返回值。

具体细节请百度

共享内存引发的问题

一个简单的示例:

#include <iostream>
#include <pthread.h>

using namespace std;

static volatile int counter = 0;

void* mythred(void* arg)
{
    cout << "begin" << static_cast<char*>(arg) << endl;
    for (int i = 0; i < 1e7; i++)
        counter += 1;
    cout << "done" << static_cast<char*>(arg) << endl;
    return nullptr;
}
int main()
{
    cout << "hello world" << endl;
    pthread_t p1, p2;
    char A[] = "A", B[] = "B";
    int rc = pthread_create(&p1, NULL, mythred, static_cast<void*>(&A));assert(rc == 0)
    rc = pthread_create(&p2, NULL, mythred, static_cast<void*>(&B));assert(rc == 0);

    pthread_join(p1, NULL);
    pthread_join(p2, NULL);
    cout << counter << endl;
}  



A,B两个线程执行相同的任务:给计数器counter增加1e7次(使用1e7是希望它包含足够多的时间片),那么直观理解,结果应该是2e7。
但结果确实:
在这里插入图片描述

三次执行结果各不相同,且均小于2e7。

分析原因

原因出在不合时宜的中断

分析这条语句:

counter += 1;

在CPU内部,这条高级语言的指令对应三条指令(取、+1、放回)

mov counter, ax
add 1, ax
mov ax, counter

假设counter值现在为50,A线程在第一次mov执行完毕后发生了上下文切换,轮到B线程对其增加,假如B线程对counter增加了20次。然后再次发生上下文切换,此时A线程恢复ax的值,从add指令开始执行,写回的counter值应该是51。
也就是说B线程的工作白费了。

核心来讲,是因为A,B线程共享内存缺乏调度引发的错误。

解决问题

一个思路是将 **counter += 1;**指令变成一条原子指令。

但对于更大的指令块呢,显然这个方式不具有扩展性。

原子:全都有或全没有,原子指令就是要么完全执行,要么还没有执行,不会出现执行一半时发生中断的情况。
原语:将具有原子性质的多条指令组成的指令块称为原语。
临界区:多个线程共同访问的变量或者代码块
静态条件:多个线程同时进入临界区,会产生不确定的结果。

对于这种A,B线程同时访问临界区的情况,更好的办法是加锁。

详见:操作系统并发性(二):锁

有时,A线程需要等待B线程执行完毕后再执行,这种情况的解决方法是条件变量。

详见:操作系统并发性(三):条件变量

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-03-15 22:13:21  更:2022-03-15 22:17:15 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 3:09:18-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码