我有一个关于基本互斥锁和解锁示例的问题!
#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;
}
当我打印&#39; cnt&#39;的价值时加入后,它有时超过1000,如1001或1002 ......
在我看来,虽然一个线程使cnt为1000,但是其他一些线程已经通过了while条件获得了互斥量,并且该值超过了最大值(1000)。
我认为在while循环中加入检查代码是不好的方法。 有没有更好的方法来解决这个问题?
答案 0 :(得分:2)
认为有4个线程正在等待获取互斥锁并同时运行。当他们到达while(cnt < 1000)
时,他们可能会或可能不会检查cnt < 1000
条件是否属于操作系统。假设它对所有人都满意,那么现在它们在while
内并准备获取锁定和增量计数。
while(cnt < 1000)
{
// --> assume that all threads are here
pthread_mutex_lock(&mutx);
cnt++;
pthread_mutex_unlock(&mutx);
}
@Edit
感谢@Jonathan Leffler,要获得正确的结果,请将其更改为
while(cnt < 1000) {
pthread_mutex_lock(&mutx);
if (cnt < 1000)
cnt++;
pthread_mutex_unlock(&mutx);
}
答案 1 :(得分:1)
以下是一些代码,其中包含问题代码,各种答案以及我对其中一个答案的评论以及测试工具。
/* 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()
,可以在GitHub上的SOQ(堆栈溢出问题)存储库中找到stderr.c
和stderr.h
中的-0
{3}}子目录。
每个-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 ]
运行程序的示例输出5次。
get_count()
那里有一些有趣的结果。变量0结果统一显示大于1000的结果,并且有一组结果非常倾斜而不是几乎均匀。我不确定是什么造成的。变量1结果强调显示一个线程只能增加计数器。变体2,3,4显示几乎均匀的分布。变量3的时间大约是其他时间的两倍,因为它执行的互斥操作数是后者的两倍 - export function fetchMlbIdsLogosColors() {
return function(dispatch) {
fetch('/mlb/mlb_ids_logos_colors')
.then(resp => {
return resp.json()
.then( json => dispatch({type: "FETCH_COLORS", json}));
})
}
}
函数中的一对和循环体中的一对。
答案 2 :(得分:0)
使用 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)
忙碌等待会占用大量的CPU周期,因为它不会移出处理器,直到它的时间片到期为止。
所以不要这样,使用 pthread条件变量来节省CPU周期。