ref http://www.ibm.com/developerworks/cn/linux/thread/posix_thread1/index.html

 #include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int myglobal;
void *thread_function(void *arg) {
int i,j;
for ( i=; i<; i++) {
j=myglobal;
j=j+;
printf(".");
fflush(stdout);
sleep();
myglobal=j;
}
return NULL;
}
int main(void) {
pthread_t mythread;
int i;
if ( pthread_create( &mythread, NULL, thread_function, NULL) ) {
printf("error creating thread.");
abort();
}
for ( i=; i<; i++) {
myglobal=myglobal+;
printf("o");
fflush(stdout);
sleep();
}
if ( pthread_join ( mythread, NULL ) ) {
printf("error joining thread.");
abort();
}
printf("\nmyglobal equals %d\n",myglobal);
exit();
}

打印输出:

$ ./thread2
..o.o.o.o.oo.o.o.o.o.o.o.o.o.o..o.o.o.o.o
myglobal equals

非常意外吧!因为 myglobal 从零开始,主线程和新线程各自对其进行了 20 次加一, 程序结束时 myglobal 值应当等于 40。由于 myglobal 输出结果为 21,这其中肯定有问题。但是究竟是什么呢?

放弃吗?好,让我来解释是怎么一回事。首先查看函数 thread_function()。注意如何将 myglobal 复制到局部变量 "j" 了吗? 接着将 j 加一, 再睡眠一秒,然后到这时才将新的 j 值复制到 myglobal?这就是关键所在。设想一下,如果主线程就在新线程将 myglobal 值复制给 j 立即将 myglobal 加一,会发生什么?当 thread_function() 将 j 的值写回 myglobal 时,就覆盖了主线程所做的修改。

当编写线程程序时,应避免产生这种无用的副作用,否则只会浪费时间(当然,除了编写关于 POSIX 线程的文章时有用)。那么,如何才能排除这种问题呢?

由于是将 myglobal 复制给 j 并且等了一秒之后才写回时产生问题,可以尝试避免使用临时局部变量并直接将 myglobal 加一。虽然这种解决方案对这个特定例子适用,但它还是不正确。如果我们对 myglobal 进行相对复杂的数学运算,而不是简单的加一,这种方法就会失效。但是为什么呢?

要理解这个问题,必须记住线程是并发运行的。即使在单处理器系统上运行(内核利用时间分片模拟多任务)也是可以的,从程序员的角度,想像两个线程是同时执行的。thread2.c 出现问题是因为 thread_function() 依赖以下论据:在 myglobal 加一之前的大约一秒钟期间不会修改 myglobal。需要有些途径让一个线程在对 myglobal 做更改时通知其它线程“不要靠近”。

个人分析:

线程 thread_function 先将global的值保存在j中,然后休眠1秒,在这一秒钟主线程做了global++的操作,然后休眠,当到thread_function 调度执行的时候i仍然保存的是1秒钱的值,然后再加加操作,覆盖了global的值。

所以要加锁

 #include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int myglobal;
pthread_mutex_t mymutex=PTHREAD_MUTEX_INITIALIZER;
void *thread_function(void *arg) {
int i,j;
for ( i=; i<; i++) {
pthread_mutex_lock(&mymutex);
j=myglobal;
j=j+;
printf(".");
fflush(stdout);
sleep();
myglobal=j;
pthread_mutex_unlock(&mymutex);
}
return NULL;
}
int main(void) {
pthread_t mythread;
int i;
if ( pthread_create( &mythread, NULL, thread_function, NULL) ) {
printf("error creating thread.");
abort();
}
for ( i=; i<; i++) {
pthread_mutex_lock(&mymutex);
myglobal=myglobal+;
pthread_mutex_unlock(&mymutex);
printf("o");
fflush(stdout);
sleep();
}
if ( pthread_join ( mythread, NULL ) ) {
printf("error joining thread.");
abort();
}
printf("\nmyglobal equals %d\n",myglobal);
exit();
}

打印输出:

o....................ooooooooooooooooooo
myglobal equals 40

  

互斥对象是这样工作的。如果线程 a 试图锁定一个互斥对象,而此时线程 b 已锁定了同一个互斥对象时,线程 a 就将进入睡眠状态。一旦线程 b 释放了互斥对象(通过 pthread_mutex_unlock() 调用),线程 a 就能够锁定这个互斥对象(换句话说,线程 a 就将从 pthread_mutex_lock() 函数调用中返回,同时互斥对象被锁定)。同样地,当线程 a 正锁定互斥对象时,如果线程 c 试图锁定互斥对象的话,线程 c 也将临时进入睡眠状态。对已锁定的互斥对象上调用 pthread_mutex_lock() 的所有线程都将进入睡眠状态,这些睡眠的线程将“排队”访问这个互斥对象。

通常使用 pthread_mutex_lock() 和 pthread_mutex_unlock() 来保护数据结构。这就是说,通过线程的锁定和解锁,对于某一数据结构,确保某一时刻只能有一个线程能够访问它。可以推测到,当线程试图锁定一个未加锁的互斥对象时,POSIX 线程库将同意锁定,而不会使线程进入睡眠状态。

所以现在的结果是正确的

许多互斥对象

如果放置了过多的互斥对象,代码就没有什么并发性可言,运行起来也比单线程解决方案慢。如果放置了过少的互斥对象,代码将出现奇怪和令人尴尬的错误。幸运的是,有一个中间立场。首先,互斥对象是用于串行化存取*共享数据*。不要对非共享数据使用互斥对象,并且,如果程序逻辑确保任何时候都只有一个线程能存取特定数据结构,那么也不要使用互斥对象。

其次,如果要使用共享数据,那么在读、写共享数据时都应使用互斥对象。用 pthread_mutex_lock() 和 pthread_mutex_unlock() 把读写部分保护起来,或者在程序中不固定的地方随机使用它们。学会从一个线程的角度来审视代码,并确保程序中每一个线程对内存的观点都是一致和合适的。为了熟悉互斥对象的用法,最初可能要花好几个小时来编写代码,但是很快就会习惯并且*也*不必多想就能够正确使用它们。

pthread 学习系列 case2-- 使用互斥锁的更多相关文章

  1. pthread 学习系列 case2-- pthread_mutex_t

    许多互斥对象 如果放置了过多的互斥对象,代码就没有什么并发性可言,运行起来也比单线程解决方案慢.如果放置了过少的互斥对象,代码将出现奇怪和令人尴尬的错误.幸运的是,有一个中间立场.首先,互斥对象是用于 ...

  2. pthread 学习系列 case1-- 共享进程数据 VS 进程

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

  3. 分布式缓存技术redis学习系列(五)——redis实战(redis与spring整合,分布式锁实现)

    本文是redis学习系列的第五篇,点击下面链接可回看系列文章 <redis简介以及linux上的安装> <详细讲解redis数据结构(内存模型)以及常用命令> <redi ...

  4. Java并发包源码学习系列:ReentrantLock可重入独占锁详解

    目录 基本用法介绍 继承体系 构造方法 state状态表示 获取锁 void lock()方法 NonfairSync FairSync 公平与非公平策略的差异 void lockInterrupti ...

  5. Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock

    本章对ReentrantLock包进行基本介绍,这一章主要对ReentrantLock进行概括性的介绍,内容包括:ReentrantLock介绍ReentrantLock函数列表ReentrantLo ...

  6. linux c学习笔记----互斥锁属性

    转自:http://lobert.iteye.com/blog/1762844 互斥锁属性 使用互斥锁(互斥)可以使线程按顺序执行.通常,互斥锁通过确保一次只有一个线程执行代码的临界段来同步多个线程. ...

  7. pthread mutex 进程间互斥锁实例

    共享标志 定义 名称 描述 0 PTHREAD_PROCESS_PRIVATE 进程内互斥锁 仅可当前进程内共享 1 PTHREAD_PROCESS_SHARED 进程间互斥锁 多个进程间共享 第一个 ...

  8. APUE学习笔记——11 线程同步、互斥锁、自旋锁、条件变量

    线程同步     同属于一个进程的不同线程是共享内存的,因而在执行过程中需要考虑数据的一致性.     假设:进程有一变量i=0,线程A执行i++,线程B执行i++,那么最终i的取值是多少呢?似乎一定 ...

  9. 互斥锁和条件变量(pthread)相关函数

    互斥锁 #include <pthread.h> // 若成功返回0,出错返回正的Exxx值 // mptr通常被初始化为PTHREAD_MUTEX_INITIALIZER int pth ...

随机推荐

  1. C语言 百炼成钢3

    //题目7:用*号输出空心菱形图案 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> # ...

  2. hp_jetdirect 9100漏洞检测

    #-*-coding=utf8-*- import socket import sys def main(): if len(sys.argv)<=1: print('Parameters er ...

  3. GeoServer 常见问题总结

    Geoserver安装环境 Geoserver在部署发布服务时,经常会遇到如下问题,现总结如下: 1.忘记了GeoServer Web Admin Page的登陆用户名和密码怎么办? 存储位置:C:\ ...

  4. Console的使用——Google Chrome代码调试

    Google Chrome控制台为开发者提供了网页和应用程序调试的几种方法,本文通过基本操作.控制台API.命令行API来介绍控制台的使用. 基本操作 1.开启控制台     可以通过下列三种方式开启 ...

  5. 怎么样快速学习AngularJS?

    其实AngularJS的官方网站首页的几个例子已经很好的展示了AngularJS的一些特性,下面我就从几个例子一步一步的讲解AngularJS吸引人的东西并且实际项目中是怎么使用ng的. 首先还是从第 ...

  6. 学习Shell脚本编程(第2期)_编写修改权限及执行Shell程序的步骤

    编写Shell程序 执行Shell程序 Shell程序有很多类似C语言和其他程序设计语言的特征,但是又没有程序语言那样复杂.Shell程序是指放在一个文件中的一系列Linux命令和实用程序.在执行的时 ...

  7. LeetCode:Unique Binary Search Trees I II

    LeetCode:Unique Binary Search Trees Given n, how many structurally unique BST's (binary search trees ...

  8. Opencv step by step - 阈值化

    Opencv里面的阈值化做起来比较简单,只需要一个函数即可: /* Applies fixed-level threshold to grayscale image. This is a basic ...

  9. IL指令大全

    IL是.NET框架中中间语言(Intermediate Language)的缩写.使用.NET框架提供的编译器可以直接将源程序编译为.exe或.dll文件,但此时编译出来的程序代码并不是CPU能直接执 ...

  10. ArrayList List<T> T[] Array

    ArrayList    其实就是一个存储obj列表的类 ArrayList 接受 null 作为有效值并且允许重复的元素. 不保证会对 ArrayList 排序. 在执行需要对 ArrayList ...