结构体位域并发问题
0x00 位域并发分析
上图是结构体BitField
变量bf
所画,需要根据打印推断出各个位域成员的存储情况,具体代码及打印见0x01 代码
和0x02 打印结果
部分。
如上图所示,GCC编译器会对位域成员进行压缩存储(经测试,VS编译器一样会压缩),比如下方示例代码中的位域成员a、b,他们存储在一个字节上。注意位域成员c、d、e及f的低两位,他们存储在一个字节。
计算机是按字节进行操作,即一个字节是基本读写单位。即使是修改一个未占满一个字节的位域成员,计算机同样是覆盖写这个成员所在的整个字节。因上述原因,当两个或多个线程并发的修改同一个字节上的不同位域成员时,在没有并发保护措施下,存在并发安全问题!
下面是验证没有并发保护情况下,一个线程修改成员d,三个线程修改成员f。从打印结果看,对d的第三次取反打印为0,应该打印是1。分析如下:
- 修改d和修改f的线程在彼此都没有执行修改前,两个线程读取第二个字节里存储的都是
0110 0101
- 线程调度,切换到修改d的线程,对d取反赋值给d,再覆盖写整个第二个字节,结果:
0111 0101
,此时还未打印,线程调度,切换到修改f的线程。 - 修改f,覆盖写整个第二个字节,这里分析就假设f的低两位写为10,此时结果为:
1010 0101
- 线程调度,切换到修改d的线程,开始打印d的值,发现打印出的d依旧是0
综上,在使用位域时,需要注意这个隐藏的并发问题!
解决上述问题有两种办法,一是对在同一个字节上的位域成员并发修改时,加锁保护。二是使用无名位域,使位域成员不存储在同一字节上即可。
参考资料:POS49-C
0x01 代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
typedef unsigned int uint32;
typedef unsigned char uint8;
typedef struct BitField
{
uint32 a : 4;
uint32 b : 4;
uint32 c : 4;
uint32 d : 1;
uint32 e : 1;
uint32 f : 4;
}BitField;
#define LOOP_CNT 10
// 对位于成员d取反
void negate(BitField *bf)
{
for(int i = 0; i < LOOP_CNT; i++)
{
bf->d = ~bf->d;