同步问题大讨论

一、修改前代码

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>


//定义线程数目为16
#define PTHREAD_NUM 16

unsigned long sum = 0;

void *thread(void *arg){
    for (int i = 0;  i < 10000; i++)
        sum += 1;  						//注意,此处没有使用原子操作
}


int main(void)
{
    printf("before ...sum = %lu \n", sum);

    pthread_t  pthread[PTHREAD_NUM];	// 被创建线程的标识
    int ret;							// 接受返回值
    void *retval[PTHREAD_NUM];

    for(int i = 0; i < PTHREAD_NUM; i++){
        ret = pthread_create(&pthread[i], NULL, thread, NULL);
        if(ret != 0){
            perror("cause:");
            printf("cause pthread %d failed. \n", i+1);
        }
    }

    for( int i = 0; i < PTHREAD_NUM; i++)
        pthread_join(pthread[i], &retval[i]);

    printf("after.... sum = %lu \n", sum);

    return 0;

}

pthread_create是创建线程的函数,函数原型:

int pthread_create(pthread_t *restrict tidp,
                   const pthread_attr_t *restrict attr,
                   void *(*start_rtn)(void), 
                   void *restrict arg);

参数说明:

​ tidp:表示指向线程标识符的指针

​ attr:用于定制各种不同的线程属性,通常直接设为NULL

​ start_rtn:新创建线程从此函数开始运行

​ arg:start_rtn函数的参数,无参数时设为NULL即可,有参数时输入参数的地址,当多于一个参数时应当使用结构体传入

返回值:成功返回0,否则返回错误码

**perror()**函数显示标准错误输出流stderr中的错误信息,该函数的格式为:

void perror( const char *message );

用来将上一个函数发生错误的原因输出到标准设备。参数 message 所指的字符串会先打印出,后面再加上错误原因字符串。此错误原因依照全局变量errno的值来决定要输出的字符串。

在库函数中有个errno变量,每个errno值对应着以字符串表示的错误类型。当你调用"某些"函数出错时,该函数已经重新设置了errno的值。perror函数只是将你输入的一些信息和errno所对应的错误一起输出。

**pthread_join()**函数用来等待一个线程的结束,线程间同步的操作。该函数以阻塞的方式等待thread指定的线程结束,当函数返回时,被等待线程的资源被收回,如果线程已经结束,那么该函数会立即返回,并且thread指定的线程必须是joinable的。函数原型:

int pthread_join(pthread_t thread, void **retval);

​ pthread_t thread:被连接线程的线程号
​ void **retval:用户定义的指针,用来存储被等待线程的返回值。

返回值 : 成功返回0,否则返回错误码

运行结果:

在这里插入图片描述

**问题:**我们本来的期望结果是16*10000, 但是发现结果不是160000,那到底为什么?如何解决?

​ 在这个代码中, 主函数循环调用16次pthread_create()函数,总共创建了16个线程,这些线程都是并发执行的,都可以访问外部公共变量sum,再加上thread函数未设置锁操作,进而导致这些线程交叉访问外部公共变量sum,最后导致了错误结果。

在这里插入图片描述

二、修改后代码

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>


//定义线程数目为16
#define PTHREAD_NUM 16

unsigned long sum = 0;

//使用原子锁
pthread_mutex_t mymutex= PTHREAD_MUTEX_INITIALIZER;		//互斥量(mymutex)就是一把锁。使用静态的方式初始化互斥锁。在LinuxThreads实现中,pthread_mutex_t是一个结构,而PTHREAD_MUTEX_INITIALIZER则是一个结构常量的宏。
void *thread(void *arg){
    for (int i = 0;  i < 10000; i++){
    	//也可以只执行下面这行代码:
    	//__sync_fetch_and_add(&sum,1);
    	pthread_mutex_lock(&mymutex);
        sum += 1;  		
        pthread_mutex_unlock(&mymutex);	
    }
}


int main(void)
{
    printf("before ...sum = %lu \n", sum);

    pthread_t  pthread[PTHREAD_NUM];	// 被创建线程的标识
    int ret;							// 接受返回值
    void *retval[PTHREAD_NUM];

    for(int i = 0; i < PTHREAD_NUM; i++){
        ret = pthread_create(&pthread[i], NULL, thread, NULL);
        if(ret != 0){
            perror("cause:");
            printf("cause pthread %d failed. \n", i+1);
        }
    }

    for( int i = 0; i < PTHREAD_NUM; i++)
        pthread_join(pthread[i], &retval[i]);

    printf("after.... sum = %lu \n", sum);

    return 0;

}

运行结果:

在这里插入图片描述

三、问答

1.什么是原子操作?

​ 所谓原子操作,就是在执行期间不可分割,要么全部执行,要么一条也不执行。在Linux下如何进行原子操作? gcc从4.1.2开始提供了__sync _*系列的build-in函数,用于提供加减和逻辑运算的原子操作,其声明如下:

type __sync_fetch_and_add (type *ptr, type value, …)

type __sync_fetch_and_sub (type *ptr, type value, …)

type __sync_fetch_and_or (type *ptr, type value, …)

2.Linux的线程如何确保原子操作?

在这里插入图片描述

3.什么是临界资源?

​ 系统中一次只允许一个进程(线程)访问的资源称为临界资源。一旦分配给进程,不能强制剥夺。

4.什么是临界区?

在这里插入图片描述

5.Linux线程如何确保临界区互斥访问?

(1)加锁保护

(2)使用原子操作

(3)编写避免竞争情况发生的代码

猜你喜欢

转载自blog.csdn.net/qq_58538265/article/details/133916550