操作系统并发性(一):线程、原子性

线程及线程与进程的区别

一个线程类似于独立的进程,只有一点区别:它们共享地址空间,从而可以访问相同的内存数据。多个线程可以运行在同一个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线程执行完毕后再执行,这种情况的解决方法是条件变量。

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值