一、修改前代码
#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)编写避免竞争情况发生的代码