面试题答案
一键面试- 使用互斥锁(Mutex)保证安全访问:
- 在C语言中,可以使用POSIX线程库(pthread)中的互斥锁来保证对
sharedData
结构体成员的安全访问。 - 示例代码如下:
- 在C语言中,可以使用POSIX线程库(pthread)中的互斥锁来保证对
#include <pthread.h>
#include <stdio.h>
// 定义共享结构体
struct shared {
int value;
char flag;
};
struct shared sharedData;
pthread_mutex_t mutex;
// 线程函数
void* thread_function(void* arg) {
// 锁定互斥锁
pthread_mutex_lock(&mutex);
// 访问和修改共享结构体成员
sharedData.value = 10;
sharedData.flag = 'A';
// 解锁互斥锁
pthread_mutex_unlock(&mutex);
return NULL;
}
int main() {
pthread_t thread;
// 初始化互斥锁
pthread_mutex_init(&mutex, NULL);
// 创建线程
pthread_create(&thread, NULL, thread_function, NULL);
// 等待线程结束
pthread_join(thread, NULL);
// 销毁互斥锁
pthread_mutex_destroy(&mutex);
return 0;
}
- C语言内存模型与原子操作的运用:
- C语言内存模型:C语言内存模型规定了程序中内存访问的语义。在多线程环境下,不同线程对共享变量的访问顺序和可见性是需要关注的问题。例如,一个线程对共享变量的修改,另一个线程何时能看到这个修改,这与内存模型相关。当使用互斥锁时,互斥锁的加锁和解锁操作建立了一种“happens - before”关系。在加锁之前对共享变量的所有修改,在解锁之后对其他线程是可见的,这保证了内存访问的一致性。
- 原子操作:虽然上述代码使用互斥锁解决了数据竞争问题,但在某些情况下,原子操作也是有用的。C11标准引入了
<stdatomic.h>
头文件,提供了原子类型和原子操作函数。对于shared
结构体中的int
和char
类型成员,可以将它们声明为原子类型,例如:
#include <stdatomic.h>
#include <pthread.h>
#include <stdio.h>
// 定义共享结构体
struct shared {
_Atomic(int) value;
_Atomic(char) flag;
};
struct shared sharedData;
pthread_mutex_t mutex;
// 线程函数
void* thread_function(void* arg) {
// 锁定互斥锁
pthread_mutex_lock(&mutex);
// 使用原子操作访问和修改共享结构体成员
atomic_store(&sharedData.value, 10);
atomic_store(&sharedData.flag, 'A');
// 解锁互斥锁
pthread_mutex_unlock(&mutex);
return NULL;
}
int main() {
pthread_t thread;
// 初始化互斥锁
pthread_mutex_init(&mutex, NULL);
// 创建线程
pthread_create(&thread, NULL, thread_function, NULL);
// 等待线程结束
pthread_join(thread, NULL);
// 销毁互斥锁
pthread_mutex_destroy(&mutex);
return 0;
}
- 原子操作保证了对变量的访问是不可分割的,不会被其他线程干扰。在使用原子操作时,即使没有互斥锁,也能保证对原子类型变量的读写操作的原子性。但是,原子操作本身并不保证不同原子操作之间的顺序一致性,在复杂场景下,仍然可能需要结合互斥锁或其他同步机制来保证整体的顺序和可见性。例如,如果需要保证对
value
和flag
的修改按顺序对其他线程可见,仅使用原子操作是不够的,还需要额外的同步机制。
通过上述互斥锁机制,并结合对C语言内存模型和原子操作的正确运用,可以保证对sharedData
结构体成员的安全访问,避免数据竞争和未定义行为。