mutex的加锁与解锁问题

zhaokai3000 2011-04-26 02:51:17
有一个进程,创建了2个线程,线程A和线程B,线程A与线程B都需要访问某一资源。
所以在进程中初始化了一个pthread_mutex_t变量resMutex;
pthread_mutex_init(&resMutex, NULL);

线程A的工作:
for (i=0;i<100;i++)
{
pthread_mutex_lock(&resMutex);
use resource
pthread_mutex_unlock(&resMutex);
}

线程B平时读消息队列,收到消息后执行一次如下操作:
pthread_mutex_lock(&resMutex);
use resource
pthread_mutex_unlock(&resMutex);

发现执行情况:
线程A lock mutex
线程A 访问资源
线程B lock mutex 阻塞
线程A unlock mutex
线程A lock mutex
线程A 访问资源
线程A unlock mutex
...
线程A的工作执行完,最后一次unlock mutex
线程B lock mutex 成功返回
线程B 访问资源
线程B unlock mutex

就是线程A工作时,线程B一直抢占不到mutex。
查了下手册,默认的mutex类型应该是PTHREAD_MUTEX_TIMED_NP,按手册来说,这种锁当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。
线程A加锁后,只有线程B请求锁,那么为什么线程A解锁后,线程B并没有马上得到锁,而是线程A下一循环周期的加锁请求又得到锁了呢?

然后我尝试了一下,在线程A解锁后加了一点延时,线程B就能够在线程A本周期解锁后得到锁了。

求解
...全文
801 16 打赏 收藏 转发到动态 举报
写回复
用AI写文章
16 条回复
切换为时间正序
请发表友善的回复…
发表回复
南京浪人甲 2011-05-07
  • 打赏
  • 举报
回复
在A锁外(加锁前或解锁后)面加一个sleep(xx),否则B是抢不到锁的
LeoricKing 2011-05-06
  • 打赏
  • 举报
回复
服务器死了 又得重写一遍。。
自己实验了下 当i==10000的时候才会出现明显的线程切换现象。

猜想:
当A线程放弃资源之前,会告诉内核,然后内核查看申请队列。如果是空,就继续让A持有资源。
如果有线程申请该资源,A线程会放弃该资源。但这是个漫长的过程。
因此在这个过程中,A继续能获得B资源。

当过了N年之后,线程B知道了 ,A资源已经跟A线程分离了 才能趁虚而入。
十三楼的说法,不能够解释A线程持续持有资源的情形。线程调度基本上有两种 1是先来先服务 2是优先级高的先服务。明显这里 用的是现来先服务。 但是B线程一直没有得到内核发出的A资源已经可以被使用的信号。所以A资源占了点便宜。能够继续占有资源。 cpu处理跟IO设备的时间差,才是操作系统存在的根本原因。时间差是王道阿。
zaghost 2011-05-05
  • 打赏
  • 举报
回复
CPU太快了吧
加个sleep(0)放弃CPU看看

据说PTHREAD_MUTEX_TIMED_NP的结尾的NP表示不可移植,不知道有影响没

nilite 2011-05-05
  • 打赏
  • 举报
回复
1、首先线程锁默认不是队列式的,每次解锁以后,所有的锁都要重新竞争。所以很有可能线程A在每次解锁以后都能抢到锁,但是可怜的线程B每次都抢不到锁。这个跟线程的调度策略有关。
2、如果不想更改默认的调度策略,那么线程A或者线程B在释放锁以后,调用sched_yield()让出cpu。
3、也可以更改默认的调度策略为SCHED_FIFO,调用函数pthread_attr_setschedpolicy(&tattr, SCHED_FIFO);具体用法请参考《linux多线程编程手册》
zhaokai3000 2011-05-03
  • 打赏
  • 举报
回复
[Quote=引用 10 楼 leoricking 的回复:]
我想可能是这样的:
线程之间切换需要切换内核级别(5000ns)
执行i<10,然后近跳转 是两个内存访问 (i的地址和 jmp地址) 大概需要30ns。

我也比较菜 可能有谬误。敬请指正!
[/Quote]

抱歉有段时间没来看。

看来我得多看看内核工作原理方面的书。
谢谢回复。
LeoricKing 2011-04-30
  • 打赏
  • 举报
回复
也就是说这个30NS是不够内核给线程B发消息。让B获得这个资源的。
LeoricKing 2011-04-30
  • 打赏
  • 举报
回复
我想可能是这样的:
线程之间切换需要切换内核级别(5000ns)
执行i<10,然后近跳转 是两个内存访问 (i的地址和 jmp地址) 大概需要30ns。

我也比较菜 可能有谬误。敬请指正!
zhaokai3000 2011-04-27
  • 打赏
  • 举报
回复
[Quote=引用 7 楼 toadzw 的回复:]
因为A的线程周期没有用完啊,一直在运行,所有一段时间内一定时A,然后一段时间内B在跑啊,如果你的处理部分大一点,会出现A,B来回跑的情况了
[/Quote]

不明白你的意思。或者你没明白我的问题。。
toadzw 2011-04-27
  • 打赏
  • 举报
回复
因为A的线程周期没有用完啊,一直在运行,所有一段时间内一定时A,然后一段时间内B在跑啊,如果你的处理部分大一点,会出现A,B来回跑的情况了
zhaokai3000 2011-04-26
  • 打赏
  • 举报
回复
up一下。继续求解
zhaokai3000 2011-04-26
  • 打赏
  • 举报
回复
thread 2 的pthread_mutex_unlock前的打印信息写错了。凑合看吧。。。
zhaokai3000 2011-04-26
  • 打赏
  • 举报
回复
呃,如果是队列方式的话,那按说线程B比线程A下一周期先请求的锁,不是应该排的比较靠前?
虽然开始我已经通过在线程A的pthread_mutex_unlock后加usleep延时一点时间解决了问题,但是还是挺奇怪。
我的这个进程是在arm11的linux下运行的(2.6.36)。

我试了一下写了个简化版的测试程序:

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

pthread_mutex_t tstmutex;

void ThreadFnc(int *threadno);

int main(void)
{
int num1;
int num2;
pthread_t thread1;
pthread_t thread2;
pthread_mutexattr_t attr;
int type;

pthread_mutexattr_init(&attr);
if (pthread_mutex_init(&tstmutex, &attr) < 0)
{
printf("pthread_mutex_init failed.\n");
return -1;
}

num1 = 1;
if (pthread_create(&thread1, NULL, (void *)ThreadFnc, (void *)&num1) < 0)
{
printf("pthread_create thread1 failed.\n");
}
usleep(5000);
num2 = 2;
if (pthread_create(&thread2, NULL, (void *)ThreadFnc, (void *)&num2) < 0)
{
printf("pthread_create thread2 failed.\n");
}

pthread_join(thread1, NULL);
pthread_join(thread2, NULL);

pthread_mutex_destroy(&tstmutex);
return 0;
}

void ThreadFnc(int *threadno)
{
int i=0;

if (*threadno == 1)
{
for(i=0; i< 5; i++)
{
printf("thread %d try to lock mutex. \n", *threadno);
if (pthread_mutex_lock(&tstmutex) < 0)
{
printf("pthread_mutex_lock failed.\n", *threadno);
}
printf("pthread_mutex_locked by thread %d.\n");
sleep(1);
printf("thread %d try to lock mutex.\n", *threadno);
if (pthread_mutex_unlock(&tstmutex) < 0)
{
printf("pthread_mutex_unlock failed.\n");
}
printf("pthread_mutex_unlocked by thread %d.\n", *threadno);
}
}
else
{
printf("thread %d try to lock mutex. \n", *threadno);
if (pthread_mutex_lock(&tstmutex) < 0)
{
printf("pthread_mutex_lock failed.\n", *threadno);
}
printf("pthread_mutex_locked by thread %d.\n");
sleep(1);
printf("thread %d try to lock mutex.\n", *threadno);
if (pthread_mutex_unlock(&tstmutex) < 0)
{
printf("pthread_mutex_unlock failed.\n");
}
printf("pthread_mutex_unlocked by thread %d.\n", *threadno);
}
}


在虚拟机上的Fedora Linux 2.6.25下运行结果固定如下:
thread 1 try to lock mutex.
pthread_mutex_locked by thread 1.
thread 2 try to lock mutex.
thread 1 try to lock mutex.
pthread_mutex_locked by thread 2.
pthread_mutex_unlocked by thread 1.
thread 1 try to lock mutex.
thread 2 try to lock mutex.
pthread_mutex_locked by thread 1.
pthread_mutex_unlocked by thread 2.
thread 1 try to lock mutex.
pthread_mutex_unlocked by thread 1.
thread 1 try to lock mutex.
pthread_mutex_locked by thread 1.
thread 1 try to lock mutex.
pthread_mutex_unlocked by thread 1.
thread 1 try to lock mutex.
pthread_mutex_locked by thread 1.
thread 1 try to lock mutex.
pthread_mutex_unlocked by thread 1.
thread 1 try to lock mutex.
pthread_mutex_locked by thread 1.
thread 1 try to lock mutex.
pthread_mutex_unlocked by thread 1.
可看出,线程1第一周期解锁后,线程2即加锁成功。

但是在arm下linux 2.6.36运行结果:
thread 1 try to lock mutex.
pthread_mutex_locked by thread 1.
thread 2 try to lock mutex.
thread 1 try to lock mutex.
pthread_mutex_unlocked by thread 1.
thread 1 try to lock mutex.
pthread_mutex_locked by thread 1.
thread 1 try to lock mutex.
pthread_mutex_unlocked by thread 1.
thread 1 try to lock mutex.
pthread_mutex_locked by thread 1.
thread 1 try to lock mutex.
pthread_mutex_unlocked by thread 1.
thread 1 try to lock mutex.
pthread_mutex_locked by thread 1.
thread 1 try to lock mutex.
pthread_mutex_unlocked by thread 1.
thread 1 try to lock mutex.
pthread_mutex_locked by thread 1.
thread 1 try to lock mutex.
pthread_mutex_unlocked by thread 1.
pthread_mutex_locked by thread 2.
thread 2 try to lock mutex.
pthread_mutex_unlocked by thread 2.

对内核是在是不懂,求高手指教。
justkk 2011-04-26
  • 打赏
  • 举报
回复
貌似mutex就是这个特性,以前见过这方面的讨论
信号灯机制就是队列方式访问的
zhaokai3000 2011-04-26
  • 打赏
  • 举报
回复
不是原子操作。
你是说默认mutex是抢占式的?
昵称很不好取 2011-04-26
  • 打赏
  • 举报
回复
抢占的吧,A线程的for循环是否是一个原子操作,如果是把锁拿到for循环外面
可以试试sleep(0)
同步概念 所谓同步,即同时起步,协调一致。不同的对象,对“同步”的理解方式略有不同。如,设备同步,是指在两个设备之间规定一个共同的时间参考;数据库同步,是指让两个或多个数据库内容保持一致,或者按需要部分保持一致;文件同步,是指让两个或多个文件夹里的文件保持一致。等等 而,编程中、通信中所说的同步与生活中大家印象中的同步概念略有差异。“同”字应是指协同、协助、互相配合。主旨在协同步调,按预定的先后次序运行。 线程同步 同步即协同步调,按预定的先后次序运行。 线程同步,指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时其它线程为保证数据一致性,不能调用该功能。 举例1: 银行存款 5000。柜台,折:取3000;提款机,卡:取 3000。剩余:2000 举例2: 内存中100字节,线程T1欲填入全1, 线程T2欲填入全0。但如果T1执行了50个字节失去cpu,T2执行,会将T1写过的内容覆盖。当T1再次获得cpu继续 从失去cpu的位置向后写入1,当执行结束,内存中的100字节,既不是全1,也不是全0。 产生的现象叫做“与时间有关的错误”(time related)。为了避免这种数据混乱,线程需要同步。 “同步”的目的,是为了避免数据混乱,解决与时间有关的错误。实际上,不仅线程间需要同步,进程间、信号间等等都需要同步机制。 因此,所有“多个控制流,共同操作一个共享资源”的情况,都需要同步。 数据混乱原因: 1. 资源共享(独享资源则不会) 2. 调度随机(意味着数据访问会出现竞争) 3. 线程间缺乏必要的同步机制。 以上3点中,前两点不能改变,欲提高效率,传递数据,资源必须共享。只要共享资源,就一定会出现竞争。只要存在竞争关系,数据就很容易出现混乱。 所以只能从第三点着手解决。使多个线程在访问共享资源的时候,出现互斥。 互斥量mutex Linux中提供一把互斥锁mutex(也称之为互斥量)。 每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。 资源还是共享的,线程间也还是竞争的, 但通过“锁”就将资源的访问变成互斥操作,而后与时间有关的错误也不会再产生了。 但,应注意:同一时刻,只能有一个线程持有该锁。 当A线程对某个全局变量加锁访问,B在访问前尝试加锁,拿不到锁,B阻塞。C线程不去加锁,而直接访问该全局变量,依然能够访问,但会出现数据混乱。 所以,互斥锁实质上是操作系统提供的一把“建议锁”(又称“协同锁”),建议程序中有多线程访问共享资源的时候使用该机制。但,并没有强制限定。 因此,即使有了mutex,如果有线程不按规则来访问数据,依然会造成数据混乱。 主要应用函数: pthread_mutex_init函数 pthread_mutex_destroy函数 pthread_mutex_lock函数 pthread_mutex_trylock函数 pthread_mutex_unlock函数 以上5个函数的返回值都是:成功返回0, 失败返回错误号。 pthread_mutex_t 类型,其本质是一个结构体。为简化理解,应用时可忽略其实现细节,简单当成整数看待。 pthread_mutex_t mutex; 变量mutex只有两种取值1、0。 pthread_mutex_init函数 初始化一个互斥锁(互斥量) ---> 初值可看作1 int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); 参1:传出参数,调用时应传 &mutex restrict关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成。不能通过除本指针以外的其他变量或指针修改 参2:互斥量属性。是一个传入参数,通常传NULL,选用默认属性(线程间共享)。 参APUE.12.4同步属性 1. 静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了static关键字修饰),可以直接使用宏进行初始化。e.g. pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER; 2. 动态初始化:局部变量应采用动态初始化。e.g. pthread_mutex_init(&mutex, NULL) pthread_mutex_destroy函数 销毁一个互斥锁 int pthread_mutex_destroy(pthread_mutex_t *mutex); pthread_mutex_lock函数 加锁。可理解为将mutex--(或-1) int pthread_mutex_lock(pthread_mutex_t *mutex); pthread_mutex_unlock函数 解锁。可理解为将mutex ++(或+1) int pthread_mutex_unlock(pthread_mutex_t *mutex); pthread_mutex_trylock函数 尝试加锁 int pthread_mutex_trylock(pthread_mutex_t *mutex); 加锁解锁 lock与unlock: lock尝试加锁,如果加锁不成功,线程阻塞,阻塞到持有该互斥量的其他线程解锁为止。 unlock主动解锁函数,同时将阻塞在该锁上的所有线程全部唤醒,至于哪个线程先被唤醒,取决于优先级、调度。默认:先阻塞、先唤醒。 例如:T1 T2 T3 T4 使用一把mutex锁。T1加锁成功,其他线程均阻塞,直至T1解锁。T1解锁后,T2 T3 T4均被唤醒,并自动再次尝试加锁。 可假想mutex锁 init成功初值为1。 lock 功能是将mutex--。 unlock将mutex++ lock与trylock: lock加锁失败会阻塞,等待锁释放。 trylock加锁失败直接返回错误号(如:EBUSY),不阻塞。 加锁步骤测试: 看如下程序:该程序是非常典型的,由于共享、竞争而没有加任何同步机制,导致产生于时间有关的错误,造成数据混乱: #include #include #include void *tfn(void *arg) { srand(time(NULL)); while (1) { printf("hello "); sleep(rand() % 3); /*模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误*/ printf("world\n"); sleep(rand() % 3); } return NULL; } int main(void) { pthread_t tid; srand(time(NULL)); pthread_create(&tid, NULL, tfn, NULL); while (1) { printf("HELLO "); sleep(rand() % 3); printf("WORLD\n"); sleep(rand() % 3); } pthread_join(tid, NULL); return 0; } 【mutex.c】 【练习】:修改该程序,使用mutex互斥锁进行同步。 1. 定义全局互斥量,初始化init(&m, NULL)互斥量,添加对应的destry 2. 两个线程while中,两次printf前后,分别加lock和unlock 3. 将unlock挪至第二个sleep后,发现交替现象很难出现。 线程在操作完共享资源后本应该立即解锁,但修改后,线程抱着锁睡眠。睡醒解锁后又立即加锁,这两个库函数本身不会阻塞。 所以在这两行代码之间失去cpu的概率很小。因此,另外一个线程很难得到加锁的机会。 4. main 中加flag = 5 将flg在while中-- 这时,主线程输出5次后试图销毁锁,但子线程未将锁释放,无法完成。 5. main 中加pthread_cancel()将子线程取消。 【pthrd_mutex.c】 结论: 在访问共享资源前加锁,访问结束后立即解锁。锁的“粒度”应越小越好。 死锁 1. 线程试图对同一个互斥量A加锁两次。 2. 线程1拥有A锁,请求获得B锁;线程2拥有B锁,请求获得A锁 【作业】:编写程序,实现上述两种死锁现象。 读写锁 与互斥量类似,但读写锁允许更高的并行性。其特性为:写独占,读共享。 读写锁状态: 一把读写锁具备三种状态: 1. 读模式下加锁状态 (读锁) 2. 写模式下加锁状态 (写锁) 3. 不加锁状态 读写锁特性: 1. 读写锁是“写模式加锁”时, 解锁前,所有对该锁加锁的线程都会被阻塞。 2. 读写锁是“读模式加锁”时, 如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞。 3. 读写锁是“读模式加锁”时, 既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式锁请求。优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高 读写锁也叫共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。写独占、读共享。 读写锁非常适合于对数据结构读的次数远大于写的情况。 主要应用函数: pthread_rwlock_init函数 pthread_rwlock_destroy函数 pthread_rwlock_rdlock函数 pthread_rwlock_wrlock函数 pthread_rwlock_tryrdlock函数 pthread_rwlock_trywrlock函数 pthread_rwlock_unlock函数 以上7 个函数的返回值都是:成功返回0, 失败直接返回错误号。 pthread_rwlock_t类型 用于定义一个读写锁变量。 pthread_rwlock_t rwlock; pthread_rwlock_init函数 初始化一把读写锁 int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); 参2:attr表读写锁属性,通常使用默认属性,传NULL即可。 pthread_rwlock_destroy函数 销毁一把读写锁 int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); pthread_rwlock_rdlock函数 以读方式请求读写锁。(常简称为:请求读锁) int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); pthread_rwlock_wrlock函数 以写方式请求读写锁。(常简称为:请求写锁) int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); pthread_rwlock_unlock函数 解锁 int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); pthread_rwlock_tryrdlock函数 非阻塞以读方式请求读写锁(非阻塞请求读锁) int pthread_

23,118

社区成员

发帖
与我相关
我的任务
社区描述
Linux/Unix社区 应用程序开发区
社区管理员
  • 应用程序开发区社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧