加锁可保证原子性操作和共享资源的可见性,但加锁成本相对较高。
mingw 编译器下,可用 mtx_t 定义一个锁,mtx_init() 函数初始化一个锁,mtx_destory() 函数销毁一个锁,mtx_lock() 函数加锁,mtx_lock() 函数解锁。
mtx_timedlock() 函数和 mtx_lock() 函数功能类似,mtx_lock()函数加锁不成功时会一直等待,而 mtx_timedlock() 函数加锁不成功时可设置等待时间。mtx_trylock() 尝试加锁,加锁成功返回锁,加锁不成功也不等待,直接返回不成功。
锁有三种类型,mtx_plain 普通锁,不支持设置等待时间。mtx_timed 智能锁,支持设置等待时间。mtx_recursive 递归锁,可以递归使用这个锁。
#include <stdio.h>
#include <time_utils.h>
#include <tinycthread.h>
#include <stdatomic.h>
typedef int (*Test)(void); // 定义函数指针 Test 类型,可指向任意参数 void 返回值为 int 的函数
// 通过函数指针调用不同函数去执行
void TestTimeInMillisecond(Test test){
long_time_t start_time = TimeInMillisecond();
test();
long_time_t end_time = TimeInMillisecond();
printf("times: %lld\n", end_time - start_time); // times: 197
}
// 使用原子类型进行 count++
atomic_int count_atomic = 0;
int CounterAtomic(void *arg){
for (int i = 0; i < 1000000; ++i) {
count_atomic++;
}
return 0;
}
int TestCounterByAtomic(){
thrd_t t_1;
thrd_t t_2;
// create two threads to run the Counter function
thrd_create(&t_1, CounterAtomic, NULL);
thrd_create(&t_2, CounterAtomic, NULL);
// free up resources
thrd_join(t_1, NULL);
thrd_join(t_2, NULL);
printf("count_atomic: %d\n", count_atomic); // count: 1049042
return 0;
}
// 使用锁进行 count++
int count_lock = 0;
mtx_t mutex; // 定义一个锁
int CounterLock(void *arg){
for (int i = 0; i < 1000000; ++i) {
mtx_lock(&mutex);
count_lock++;
mtx_unlock(&mutex);
}
return 0;
}
void TestCounterByLock(){
thrd_t t_1;
thrd_t t_2;
// create two threads to run the Counter function
thrd_create(&t_1, CounterLock, NULL);
thrd_create(&t_2, CounterLock, NULL);
// free up resources
thrd_join(t_1, NULL);
thrd_join(t_2, NULL);
printf("count_lock: %d\n", count_lock);
mtx_destroy(&mutex);
}
int main(){
// 查看原子类型消耗的时间
Test test_atomic = TestCounterByAtomic;
TestTimeInMillisecond(test_atomic);
// 加锁耗费的时间
mtx_init(&mutex, mtx_plain); // 初始化锁
Test test_lock = TestCounterByLock;
TestTimeInMillisecond(test_lock);
return 0;
}
注意,代码书写过程中使用了函数指针,将函数作为参数传入到其它函数中使用。可将其看做对象去理解,一个函数中需要执行两个对象的方法,这两个对象的类型是一致的,故传入参数应为对象,再去执行传入对象中的方法。类比于函数指针,两个函数的结构是一样的,所以可用函数指针去替代两个函数,之后执行该函数指针指向的方法即可。程序执行结果如下图所示,可以发现加锁的性能消耗大于操作 atomic_int 定义的原子类型变量。