如何控制互斥量的加锁和解锁?
How to control mutex lock and unlock?
我有一个关于基本互斥锁和解锁示例的问题!
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#define TNUM 4
pthread_mutext_t mutx;
int cnt = 0;
void *t_function(void *data)
{
while(cnt < 1000)
{
pthread_mutex_lock(&mutx);
cnt++;
pthread_mutex_unlock(&mutx);
}
}
int main()
{
pthread_t p_thread[TNUM];
int thr_id[TNUM];
int status;
int i;
clock_t start, end;
status = pthread_mutex_init(&mutx, NULL);
start = clock();
for(i=0; i<TNUM; i++)
{
thr_id[i] = pthread_create(&p_thread[i], NULL, t_function, NULL);
if(thr_id[i] < 0)
{
perror("thread create error: ");
exit(i);
}
}
for(i=0; i<TNUM; i++)
{
pthread_join(p_thread[i], (void**)&status);
}
end = clock();
printf("time : %lf\n", (double)(end-start)/CLOCKS_PER_SEC);
printf("result : %d\n", cnt);
return 0;
}
当我在加入后打印 'cnt' 的值时,它有时会超过 1000,例如 1001 或 1002 ....
在我看来,虽然一个线程使 cnt 为 1000,但其他一些线程已经通过 while 条件获得互斥量并且该值超过最大值(1000)。
我认为只在 while 循环中添加检查代码是不好的方法。
有没有更好的方法来解决这个问题?
认为有 4 个线程正在等待同时获取互斥量和 运行。当他们达到 while(cnt < 1000)
时,他们可能会或可能不会检查 cnt < 1000
受 OS 约束的条件。假设所有这些都满足,那么现在它们在 while
内并准备好获取锁和递增计数。
while(cnt < 1000)
{
// --> assume that all threads are here
pthread_mutex_lock(&mutx);
cnt++;
pthread_mutex_unlock(&mutx);
}
@编辑
感谢@Jonathan Leffler,要获得正确的结果,请像这样更改它
while(cnt < 1000) {
pthread_mutex_lock(&mutx);
if (cnt < 1000)
cnt++;
pthread_mutex_unlock(&mutx);
}
使用 pthread 互斥体 执行原子操作(一次仅对同一变量执行一项操作)。当 mutex
被 one thread
锁定时,来自 other thread
的所有其他锁定请求将阻止 thread(which took the lock)
,在完成所有操作后自行解锁。
这是示例 t_function()
void *t_function(void *data) {
printf(" %s is doing something \n",__func__);
while(1) {
pthread_mutex_lock(&mutx);
if(cnt == 1000) {
printf("ready ..other can do \n");
break;
}
cnt++;
pthread_mutex_unlock(&mutx);
printf("waiting for cnt to reach 1000\n");
}
pthread_exit(0);
}
但是这里 while(1)
是 Busy Wait 消耗大量 CPU 周期,因为它在其时间片到期之前不会移出处理器。
因此,使用 pthread 条件变量 来代替这个,以节省 CPU 个周期。
这里是一些代码,其中包含来自问题的代码、来自各种答案的代码,以及来自我对其中一个答案所做的评论的代码——加上一个测试工具。
/* SO 4972-0718 */
#include "posixver.h"
#include "stderr.h"
#include <errno.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#define TNUM 4
static pthread_mutex_t mutx = PTHREAD_MUTEX_INITIALIZER;
static int cnt = 0;
static int trace = 0;
/* Code from question */
static void *t_function_0(void *data)
{
int tid = (uintptr_t)data;
int inc = 0;
while (cnt < 1000)
{
pthread_mutex_lock(&mutx);
cnt++;
pthread_mutex_unlock(&mutx);
inc++;
if (trace) printf("%d\n", tid);
}
if (trace) printf("%d done (%d increments)\n", tid, inc);
return (void *)(uintptr_t)inc;
}
/* Original code from answer by @snr */
static void *t_function_1(void *data)
{
int tid = (uintptr_t)data;
int inc = 0;
pthread_mutex_lock(&mutx);
while (cnt < 1000)
{
cnt++;
if (trace) printf("%d\n", tid);
inc++;
}
pthread_mutex_unlock(&mutx);
if (trace) printf("%d done (%d increments)\n", tid, inc);
return (void *)(uintptr_t)inc;
}
/* Revised code from answer by @snr */
static void *t_function_2(void *data)
{
int tid = (uintptr_t)data;
int inc = 0;
while (cnt < 1000)
{
pthread_mutex_lock(&mutx);
if (cnt < 1000)
{
cnt++;
inc++;
}
pthread_mutex_unlock(&mutx);
if (trace) printf("%d\n", tid);
}
if (trace) printf("%d done (%d increments)\n", tid, inc);
return (void *)(uintptr_t)inc;
}
/* Support function for commentary answer by JL */
static int get_count(void)
{
pthread_mutex_lock(&mutx);
int cnt_val = cnt;
pthread_mutex_unlock(&mutx);
return cnt_val;
}
/* Code from commentary answer by JL - only reading cnt when mutex is locked */
static void *t_function_3(void *data)
{
int tid = (uintptr_t)data;
int inc = 0;
while (get_count() < 1000)
{
pthread_mutex_lock(&mutx);
if (cnt < 1000)
{
cnt++;
inc++;
}
pthread_mutex_unlock(&mutx);
if (trace) printf("%d\n", tid);
}
if (trace) printf("%d done (%d increments)\n", tid, inc);
return (void *)(uintptr_t)inc;
}
/* 'Esoteric' code from commentary answer by JL - only reading cnt when mutex is locked */
static void *t_function_4(void *data)
{
int tid = (uintptr_t)data;
int inc = 0;
int copy_cnt = 0;
while (copy_cnt < 1000)
{
pthread_mutex_lock(&mutx);
if (cnt < 1000)
{
cnt++;
inc++;
}
copy_cnt = cnt;
pthread_mutex_unlock(&mutx);
if (trace) printf("%d\n", tid);
}
if (trace) printf("%d done (%d increments)\n", tid, inc);
return (void *)(uintptr_t)inc;
}
static const char optstr[] = "t01234";
static const char usestr[] = "[-t01234]";
int main(int argc, char **argv)
{
err_setarg0(argv[0]);
static void *(*functions[])(void *) =
{
t_function_0, t_function_1, t_function_2,
t_function_3, t_function_4,
};
int variant = 0;
int opt;
while ((opt = getopt(argc, argv, optstr)) != -1)
{
switch (opt)
{
case '0':
case '1':
case '2':
case '3':
case '4':
variant = opt - '0';
break;
case 't':
trace = 1;
break;
default:
err_usage(usestr);
}
}
printf("Variant %d: ", variant);
fflush(stdout);
pthread_t p_thread[TNUM];
int thr_id[TNUM];
clock_t start = clock();
for (int i = 0; i < TNUM; i++)
{
thr_id[i] = pthread_create(&p_thread[i], NULL, functions[variant], (void *)(uintptr_t)i);
if (thr_id[i] < 0)
{
errno = thr_id[i];
err_syserr("failed to create thread %d\n", i);
}
}
int inc[TNUM];
for (int i = 0; i < TNUM; i++)
{
void *vp;
pthread_join(p_thread[i], &vp);
inc[i] = (int)(uintptr_t)vp;
if (trace) printf("Join %d: %d increments\n", i, inc[i]);
}
clock_t end = clock();
printf("time : %.6lfs ", (double)(end - start) / CLOCKS_PER_SEC);
printf("result : %d ", cnt);
const char *pad = " [ ";
for (int i = 0; i < TNUM; i++)
{
printf("%s%d", pad, inc[i]);
pad = ", ";
}
printf(" ]\n");
return 0;
}
err_syserr()
等报错功能的代码可以在我的SOQ (Stack Overflow Questions) repository on GitHub as files stderr.c
and stderr.h
in the src/libsoqsub-directory.
中找到
运行 程序的输出示例 5 次 -0
、-1
、-2
、-3
和 -4
。
Variant 0: time : 0.006511s result : 1003 [ 251, 251, 251, 250 ]
Variant 0: time : 0.007028s result : 1003 [ 251, 251, 251, 250 ]
Variant 0: time : 0.006156s result : 1003 [ 333, 224, 223, 223 ]
Variant 0: time : 0.006656s result : 1003 [ 251, 251, 250, 251 ]
Variant 0: time : 0.006931s result : 1003 [ 252, 250, 250, 251 ]
Variant 1: time : 0.000462s result : 1000 [ 0, 0, 1000, 0 ]
Variant 1: time : 0.000345s result : 1000 [ 1000, 0, 0, 0 ]
Variant 1: time : 0.000345s result : 1000 [ 1000, 0, 0, 0 ]
Variant 1: time : 0.000388s result : 1000 [ 1000, 0, 0, 0 ]
Variant 1: time : 0.000340s result : 1000 [ 1000, 0, 0, 0 ]
Variant 2: time : 0.006203s result : 1000 [ 251, 250, 249, 250 ]
Variant 2: time : 0.006779s result : 1000 [ 250, 250, 250, 250 ]
Variant 2: time : 0.006841s result : 1000 [ 251, 250, 250, 249 ]
Variant 2: time : 0.005960s result : 1000 [ 251, 250, 250, 249 ]
Variant 2: time : 0.006416s result : 1000 [ 250, 250, 250, 250 ]
Variant 3: time : 0.012238s result : 1000 [ 250, 250, 250, 250 ]
Variant 3: time : 0.012763s result : 1000 [ 250, 250, 250, 250 ]
Variant 3: time : 0.013417s result : 1000 [ 250, 250, 250, 250 ]
Variant 3: time : 0.012676s result : 1000 [ 250, 250, 250, 250 ]
Variant 3: time : 0.012899s result : 1000 [ 250, 250, 250, 250 ]
Variant 4: time : 0.005999s result : 1000 [ 250, 250, 250, 250 ]
Variant 4: time : 0.006461s result : 1000 [ 251, 250, 250, 249 ]
Variant 4: time : 0.006112s result : 1000 [ 250, 250, 250, 250 ]
Variant 4: time : 0.005910s result : 1000 [ 251, 249, 250, 250 ]
Variant 4: time : 0.006832s result : 1000 [ 250, 250, 250, 250 ]
里面有一些有趣的结果。变体 0 结果一致地显示大于 1000 的结果,并且有一组结果非常偏斜而不是几乎一致。我不确定是什么原因造成的。变体 1 结果着重表明一个线程只能递增计数器。变体 2、3、4 显示出几乎均匀的分布。变体 3 的时间大约是其他变体的两倍,因为它执行的互斥操作是其他变体的两倍——一对在 get_count()
函数中,一对在循环体中。
我有一个关于基本互斥锁和解锁示例的问题!
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#define TNUM 4
pthread_mutext_t mutx;
int cnt = 0;
void *t_function(void *data)
{
while(cnt < 1000)
{
pthread_mutex_lock(&mutx);
cnt++;
pthread_mutex_unlock(&mutx);
}
}
int main()
{
pthread_t p_thread[TNUM];
int thr_id[TNUM];
int status;
int i;
clock_t start, end;
status = pthread_mutex_init(&mutx, NULL);
start = clock();
for(i=0; i<TNUM; i++)
{
thr_id[i] = pthread_create(&p_thread[i], NULL, t_function, NULL);
if(thr_id[i] < 0)
{
perror("thread create error: ");
exit(i);
}
}
for(i=0; i<TNUM; i++)
{
pthread_join(p_thread[i], (void**)&status);
}
end = clock();
printf("time : %lf\n", (double)(end-start)/CLOCKS_PER_SEC);
printf("result : %d\n", cnt);
return 0;
}
当我在加入后打印 'cnt' 的值时,它有时会超过 1000,例如 1001 或 1002 ....
在我看来,虽然一个线程使 cnt 为 1000,但其他一些线程已经通过 while 条件获得互斥量并且该值超过最大值(1000)。
我认为只在 while 循环中添加检查代码是不好的方法。 有没有更好的方法来解决这个问题?
认为有 4 个线程正在等待同时获取互斥量和 运行。当他们达到 while(cnt < 1000)
时,他们可能会或可能不会检查 cnt < 1000
受 OS 约束的条件。假设所有这些都满足,那么现在它们在 while
内并准备好获取锁和递增计数。
while(cnt < 1000)
{
// --> assume that all threads are here
pthread_mutex_lock(&mutx);
cnt++;
pthread_mutex_unlock(&mutx);
}
@编辑
感谢@Jonathan Leffler,要获得正确的结果,请像这样更改它
while(cnt < 1000) {
pthread_mutex_lock(&mutx);
if (cnt < 1000)
cnt++;
pthread_mutex_unlock(&mutx);
}
使用 pthread 互斥体 执行原子操作(一次仅对同一变量执行一项操作)。当 mutex
被 one thread
锁定时,来自 other thread
的所有其他锁定请求将阻止 thread(which took the lock)
,在完成所有操作后自行解锁。
这是示例 t_function()
void *t_function(void *data) {
printf(" %s is doing something \n",__func__);
while(1) {
pthread_mutex_lock(&mutx);
if(cnt == 1000) {
printf("ready ..other can do \n");
break;
}
cnt++;
pthread_mutex_unlock(&mutx);
printf("waiting for cnt to reach 1000\n");
}
pthread_exit(0);
}
但是这里 while(1)
是 Busy Wait 消耗大量 CPU 周期,因为它在其时间片到期之前不会移出处理器。
因此,使用 pthread 条件变量 来代替这个,以节省 CPU 个周期。
这里是一些代码,其中包含来自问题的代码、来自各种答案的代码,以及来自我对其中一个答案所做的评论的代码——加上一个测试工具。
/* SO 4972-0718 */
#include "posixver.h"
#include "stderr.h"
#include <errno.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#define TNUM 4
static pthread_mutex_t mutx = PTHREAD_MUTEX_INITIALIZER;
static int cnt = 0;
static int trace = 0;
/* Code from question */
static void *t_function_0(void *data)
{
int tid = (uintptr_t)data;
int inc = 0;
while (cnt < 1000)
{
pthread_mutex_lock(&mutx);
cnt++;
pthread_mutex_unlock(&mutx);
inc++;
if (trace) printf("%d\n", tid);
}
if (trace) printf("%d done (%d increments)\n", tid, inc);
return (void *)(uintptr_t)inc;
}
/* Original code from answer by @snr */
static void *t_function_1(void *data)
{
int tid = (uintptr_t)data;
int inc = 0;
pthread_mutex_lock(&mutx);
while (cnt < 1000)
{
cnt++;
if (trace) printf("%d\n", tid);
inc++;
}
pthread_mutex_unlock(&mutx);
if (trace) printf("%d done (%d increments)\n", tid, inc);
return (void *)(uintptr_t)inc;
}
/* Revised code from answer by @snr */
static void *t_function_2(void *data)
{
int tid = (uintptr_t)data;
int inc = 0;
while (cnt < 1000)
{
pthread_mutex_lock(&mutx);
if (cnt < 1000)
{
cnt++;
inc++;
}
pthread_mutex_unlock(&mutx);
if (trace) printf("%d\n", tid);
}
if (trace) printf("%d done (%d increments)\n", tid, inc);
return (void *)(uintptr_t)inc;
}
/* Support function for commentary answer by JL */
static int get_count(void)
{
pthread_mutex_lock(&mutx);
int cnt_val = cnt;
pthread_mutex_unlock(&mutx);
return cnt_val;
}
/* Code from commentary answer by JL - only reading cnt when mutex is locked */
static void *t_function_3(void *data)
{
int tid = (uintptr_t)data;
int inc = 0;
while (get_count() < 1000)
{
pthread_mutex_lock(&mutx);
if (cnt < 1000)
{
cnt++;
inc++;
}
pthread_mutex_unlock(&mutx);
if (trace) printf("%d\n", tid);
}
if (trace) printf("%d done (%d increments)\n", tid, inc);
return (void *)(uintptr_t)inc;
}
/* 'Esoteric' code from commentary answer by JL - only reading cnt when mutex is locked */
static void *t_function_4(void *data)
{
int tid = (uintptr_t)data;
int inc = 0;
int copy_cnt = 0;
while (copy_cnt < 1000)
{
pthread_mutex_lock(&mutx);
if (cnt < 1000)
{
cnt++;
inc++;
}
copy_cnt = cnt;
pthread_mutex_unlock(&mutx);
if (trace) printf("%d\n", tid);
}
if (trace) printf("%d done (%d increments)\n", tid, inc);
return (void *)(uintptr_t)inc;
}
static const char optstr[] = "t01234";
static const char usestr[] = "[-t01234]";
int main(int argc, char **argv)
{
err_setarg0(argv[0]);
static void *(*functions[])(void *) =
{
t_function_0, t_function_1, t_function_2,
t_function_3, t_function_4,
};
int variant = 0;
int opt;
while ((opt = getopt(argc, argv, optstr)) != -1)
{
switch (opt)
{
case '0':
case '1':
case '2':
case '3':
case '4':
variant = opt - '0';
break;
case 't':
trace = 1;
break;
default:
err_usage(usestr);
}
}
printf("Variant %d: ", variant);
fflush(stdout);
pthread_t p_thread[TNUM];
int thr_id[TNUM];
clock_t start = clock();
for (int i = 0; i < TNUM; i++)
{
thr_id[i] = pthread_create(&p_thread[i], NULL, functions[variant], (void *)(uintptr_t)i);
if (thr_id[i] < 0)
{
errno = thr_id[i];
err_syserr("failed to create thread %d\n", i);
}
}
int inc[TNUM];
for (int i = 0; i < TNUM; i++)
{
void *vp;
pthread_join(p_thread[i], &vp);
inc[i] = (int)(uintptr_t)vp;
if (trace) printf("Join %d: %d increments\n", i, inc[i]);
}
clock_t end = clock();
printf("time : %.6lfs ", (double)(end - start) / CLOCKS_PER_SEC);
printf("result : %d ", cnt);
const char *pad = " [ ";
for (int i = 0; i < TNUM; i++)
{
printf("%s%d", pad, inc[i]);
pad = ", ";
}
printf(" ]\n");
return 0;
}
err_syserr()
等报错功能的代码可以在我的SOQ (Stack Overflow Questions) repository on GitHub as files stderr.c
and stderr.h
in the src/libsoqsub-directory.
运行 程序的输出示例 5 次 -0
、-1
、-2
、-3
和 -4
。
Variant 0: time : 0.006511s result : 1003 [ 251, 251, 251, 250 ]
Variant 0: time : 0.007028s result : 1003 [ 251, 251, 251, 250 ]
Variant 0: time : 0.006156s result : 1003 [ 333, 224, 223, 223 ]
Variant 0: time : 0.006656s result : 1003 [ 251, 251, 250, 251 ]
Variant 0: time : 0.006931s result : 1003 [ 252, 250, 250, 251 ]
Variant 1: time : 0.000462s result : 1000 [ 0, 0, 1000, 0 ]
Variant 1: time : 0.000345s result : 1000 [ 1000, 0, 0, 0 ]
Variant 1: time : 0.000345s result : 1000 [ 1000, 0, 0, 0 ]
Variant 1: time : 0.000388s result : 1000 [ 1000, 0, 0, 0 ]
Variant 1: time : 0.000340s result : 1000 [ 1000, 0, 0, 0 ]
Variant 2: time : 0.006203s result : 1000 [ 251, 250, 249, 250 ]
Variant 2: time : 0.006779s result : 1000 [ 250, 250, 250, 250 ]
Variant 2: time : 0.006841s result : 1000 [ 251, 250, 250, 249 ]
Variant 2: time : 0.005960s result : 1000 [ 251, 250, 250, 249 ]
Variant 2: time : 0.006416s result : 1000 [ 250, 250, 250, 250 ]
Variant 3: time : 0.012238s result : 1000 [ 250, 250, 250, 250 ]
Variant 3: time : 0.012763s result : 1000 [ 250, 250, 250, 250 ]
Variant 3: time : 0.013417s result : 1000 [ 250, 250, 250, 250 ]
Variant 3: time : 0.012676s result : 1000 [ 250, 250, 250, 250 ]
Variant 3: time : 0.012899s result : 1000 [ 250, 250, 250, 250 ]
Variant 4: time : 0.005999s result : 1000 [ 250, 250, 250, 250 ]
Variant 4: time : 0.006461s result : 1000 [ 251, 250, 250, 249 ]
Variant 4: time : 0.006112s result : 1000 [ 250, 250, 250, 250 ]
Variant 4: time : 0.005910s result : 1000 [ 251, 249, 250, 250 ]
Variant 4: time : 0.006832s result : 1000 [ 250, 250, 250, 250 ]
里面有一些有趣的结果。变体 0 结果一致地显示大于 1000 的结果,并且有一组结果非常偏斜而不是几乎一致。我不确定是什么原因造成的。变体 1 结果着重表明一个线程只能递增计数器。变体 2、3、4 显示出几乎均匀的分布。变体 3 的时间大约是其他变体的两倍,因为它执行的互斥操作是其他变体的两倍——一对在 get_count()
函数中,一对在循环体中。