线程_带来的问题

版权声明:私藏源代码是违反人性的罪恶行为!博客转载无需告知,学无止境。 https://blog.csdn.net/qq_41822235/article/details/83515476

目录

一、 数据共享

二、 语句不具有原子性


一、 数据共享

数据指的是全局、静态、堆区、文件描述符等。

#include<stdio.h>
#include<pthread.h>
#include<string.h>  //strtok()
#include<assert.h>  //assert()

/*thr_fun1线程切割线程内的字符串*/
void* thr_fun1(void* arg)
{
    char buf[] = "a b c d";
    sleep(2);
    char* p = strtok(buf, " ");
    while(NULL != p)
    {
	printf("thr_fun pthread: %s\n", p);
	sleep(1);
	p = strtok(NULL, " ");
    }
}

/*理想中main主线程切割主线程的字符串*/
int main()
{
    pthread_t tid;
    int res = pthread_create(&tid, NULL, thr_fun1, NULL);
    assert(0 == res);
    char buf[] = "0 1 2 3 4 5 6 7 8 9 ";
    char* p = strtok(buf, " ");
    while(NULL != p)
    {
	printf("main pthread: %s\n", p);
	sleep(2);
	p = strtok(NULL, " ");
    }
   pthread_join(tid, NULL);
}

初看之下,main主线程和thr_fun1函数线程之间并没有共享数据,但是strtok内部实现时,会定义一个静态变量来记录下一次的开始位置。正是因为局部静态变量的存在,所以切割字符串就存在问题。理想情况下, main主线程一次输出0 1 2 3 4 5 6 7 8 9 ,thr_fun1函数线程依次输出a b c d,并发的含义是两个线程会交替执行,但是只看单个线程而言,运行是从上一次暂停的位置继续向下执行的。

图1 反常的结果(Linux)

图1 告诉我们strtok函数在多线程情况下是不安全的,但是运行结果不一定就是非要和预想中的结果不同(全凭天意)。不能把希望寄托在程序这次能够被正确的执行不出错,我们要求的是,任何环境下都要百分百正确!

二、 语句不具有原子性

源文件经过预编译、编译、汇编、链接生成可执行文件。编译过程会生成汇编指令,而汇编过程是根据汇编指令和机器指令的对照表意义翻译。关键在于汇编指令是一条条去执行的,但是一条语句可能对应多条汇编指令。就有可能导致一条语句在几个时刻才能执行完。

在多核环境下,线程与线程间是并行运行。如图2 所示,变量a的初始值为0。线程1和线程2都对a执行10000次后置++,我们预想a的值会变成20000,而实际上a的值∈[10 000,20 000],何解?线程1先执行指令1和指令2,寄存器中的值为1;线程2顺利执行3条指令,将内存中的值改为1;线程1现在执行指令3,取出eax寄存器中的值放入内存中,a的值还是1。两个线程以此顺序执行10000次,a的值变为10000,这是最小值的情况。

图2 从汇编层次看后置++
#include<stdio.h>   //printf()
#include<pthread.h>
#include<assert.h>  //assert()
#include<unistd.h>

void* thr_fun1(void* arg)        //函数线程
{
    int *p = arg;
    for(int i = 0; i < 10000; ++i)
    {
	*p += 1;
    }
}

int main()        //主线程
{
    pthread_t tid;
    int a = 0;
    int res = pthread_create(&tid, NULL, thr_fun1, (void*)&a);
    assert(0 == res);

    for(int i = 0; i < 10000; ++i)
    {
	a += 1;
	a += 2;
    }
    pthread_join(tid, NULL);    //等函数线程运行结束,主线程才能输出a的值。
    printf("a = %d\n", a);
}

如图3 所示,预期的结果是40000,但是连续几次运行程序,虽然结果以40000居多——根源是因为指令执行速度太快,3条汇编指令很少会被打断。但是也会出现反常的结果,分析过程和图2 类似。  

图3 后置++的反常(2核4线程Linux)

猜你喜欢

转载自blog.csdn.net/qq_41822235/article/details/83515476