如何控制互斥锁和解锁?

时间:2018-04-08 17:34:19

标签: c linux mutex

我有一个关于基本互斥锁和解锁示例的问题!

#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循环中加入检查代码是不好的方法。 有没有更好的方法来解决这个问题?

3 个答案:

答案 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.cstderr.h中的-0 {3}}子目录。

每个-1-2-3-4Variant 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互斥锁执行原子操作(同一变量上一次只能执行一次操作)。当mutexone 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周期。