1. 程式人生 > >第11章——《執行緒》(2)

第11章——《執行緒》(2)

實驗環境介紹

  • gcc:4.8.5
  • glibc:glibc-2.17-222.el7.x86_64
  • os:Centos7.4
  • kernel:3.10.0-693.21.1.el7.x86_64

執行緒同步

  • 執行緒同步解決的問題:同一個執行緒的不同執行緒共享相同的記憶體時,為了解決資料不一致的競爭問題,需要使用同步機制來確保資料的一致性
互斥量
  • 基本使用
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h> struct foo { int f_count; pthread_mutex_t f_lock; int f_id; /* ... more stuff here ... */ }; struct foo * foo_alloc(int id) /* allocate the object */ { struct foo *fp; if ((fp = malloc(sizeof(struct foo))) != NULL) { fp->f_count = 1; fp->
f_id = id; if (pthread_mutex_init(&fp->f_lock, NULL) != 0) { free(fp); return(NULL); } /* ... continue initialization ... */ } printf("alloc a foo\n"); return(fp); } void foo_hold(struct foo *fp) /* add a reference to the object */ { pthread_mutex_lock
(&fp->f_lock); printf("thread %lu lock foo\n", pthread_self()); fp->f_count++; sleep(3); pthread_mutex_unlock(&fp->f_lock); printf("thread %lu unlock foo\n", pthread_self()); } void foo_rele(struct foo *fp) /* release a reference to the object */ { pthread_mutex_lock(&fp->f_lock); if (--fp->f_count == 0) { /* last reference */ pthread_mutex_unlock(&fp->f_lock); pthread_mutex_destroy(&fp->f_lock); free(fp); printf("free the foo\n"); } else { pthread_mutex_unlock(&fp->f_lock); } } void * thr_fn1(void *arg) { struct foo *fp = (struct foo *)arg; foo_hold(fp); return (void *)1; } void * thr_fn2(void *arg) { sleep(1); struct foo *fp = (struct foo *)arg; foo_hold(fp); return (void *)1; } int main(void) { pthread_t tid1, tid2; int err; struct foo *fp = foo_alloc(1); pthread_create(&tid1, NULL, thr_fn1, (void *)fp); pthread_create(&tid2, NULL, thr_fn2, (void *)fp); sleep(10); printf("fp count = %d\n", fp->f_count); foo_rele(fp); foo_rele(fp); foo_rele(fp); return 0; } result: alloc a foo thread 140042528159488 lock foo thread 140042528159488 unlock foo thread 140042519766784 lock foo thread 140042519766784 unlock foo fp count = 3 free the foo
  • 避免死鎖
    • 如果一個執行緒對一個互斥量連續加鎖兩次,那麼自身會陷入死鎖狀態
    • 如果有兩個互斥量,那麼多個執行緒對其進行加鎖和解鎖時,都應該按照相同的順序進行操作
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

struct foo {
    int f_count;
    pthread_mutex_t f_lock;
    int f_id;
    /* ... more stuff here ... */
};

struct foo *
foo_alloc(int id) /* allocate the object */
{
    struct foo *fp;

    if ((fp = malloc(sizeof(struct foo))) != NULL) {
        fp->f_count = 1;
        fp->f_id = id;
        if (pthread_mutex_init(&fp->f_lock, NULL) != 0) {
            free(fp);
            return(NULL);
        }
        /* ... continue initialization ... */
    }
    printf("alloc a foo\n");
    return(fp);
}

int
main(void)
{
    pthread_t tid1, tid2;
    int err;
    struct foo *fp = foo_alloc(1);

    if (pthread_mutex_init(&(fp->f_lock), NULL) != 0) {
        free(fp);
        return(NULL);
    }
    pthread_mutex_lock(&(fp->f_lock));
    pthread_mutex_lock(&(fp->f_lock));
    printf("after lock fp->f_lock twice\n");                // 此時這裡不會列印

    pthread_mutex_unlock(&(fp->f_lock));
    pthread_mutex_unlock(&(fp->f_lock));
    printf("after unlock fp->f_lock twice\n");

    return 0;
}
  • 使用pthread_mutex_timedlock避免永久性阻塞
#include <pthread.h>
#include <pthread.h>
#include <time.h>
#include <sys/time.h>

#define USECTONSEC 1000 /* microseconds to nanoseconds */

void
clock_gettime_new(int id, struct timespec *tsp);

int
main(void)
{
    int err;
    struct timespec tout;
    struct tm *tmp;
    char buf[64];
    pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

    pthread_mutex_lock(&lock);
    printf("mutex is locked\n");
    clock_gettime_new(CLOCK_REALTIME, &tout);
    tmp = localtime(&tout.tv_sec);
    strftime(buf, sizeof(buf), "%r", tmp);
    printf("current time is %s\n", buf);
    tout.tv_sec += 10; /* 10 seconds from now */
    /* caution: this could lead to deadlock */
    err = pthread_mutex_timedlock(&lock, &tout);
    clock_gettime_new(CLOCK_REALTIME, &tout);
    tmp = localtime(&tout.tv_sec);
    strftime(buf, sizeof(buf), "%r", tmp);
    printf("the time is now %s\n", buf);
    if (err == 0)
        printf("mutex locked again!\n");
    else
        printf("can't lock mutex again: %d\n", err);
    exit(0);
}

void
clock_gettime_new(int id, struct timespec *tsp)
{
    struct timeval tv;

    gettimeofday(&tv, NULL);
    tsp->tv_sec = tv.tv_sec;
    tsp->tv_nsec = tv.tv_usec * USECTONSEC;
}
  • 讀寫鎖
    • 讀寫鎖有三種狀態:一次只有一個執行緒可以佔有寫模式的讀寫鎖,但是多個執行緒可以同時佔有讀模式的讀寫鎖
      • 讀模式下加鎖:所有試圖以讀模式對它進行加鎖的執行緒都可以得到訪問權,但是以寫模式對它進行枷鎖的執行緒將阻塞;
      • 寫模式下加鎖:在這個鎖被解鎖之前,所有試圖對這個鎖加鎖的執行緒都會被阻塞
      • 不加鎖狀態
    • 讀寫鎖非常適合於對資料結構讀的次數遠大於寫的情況。
/*************************************************************
 * pthread_rwlock_test2.c:驗證讀寫鎖的預設順序
 * 如果在main函式中用pthread_rwlock_wrlock上鎖,那麼
 * 如果所有執行緒都阻塞在寫鎖上的時候,優先處理的是被阻塞的寫鎖
 * 然後才處理讀出鎖
 * 如果在main函式中用pthread_rwlock_rdlock上鎖,那麼
 * 如果有讀者正在讀的時候即使後面到來的寫者比另外一些到來的讀者更早
 * 也是先處理完讀者,才轉而處理寫者,這樣會導致寫飢餓
 * 
 * 由此(執行結果)可以看出,LINUX平臺預設的是讀者優先,如果想要以寫者優先
 * 則需要做一些處理
 **************************************************************/
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>

pthread_rwlock_t rwlock;

void *readers(void *arg)
{
    pthread_rwlock_rdlock(&rwlock);
    printf("reader %d got the lock\n", (int)arg);
    pthread_rwlock_unlock(&rwlock);
    //return NULL;
}
void *writers(void *arg)
{
    pthread_rwlock_wrlock(&rwlock);
    printf("writer %d got the lock\n", (int)arg);
    pthread_rwlock_unlock(&rwlock);
    //return NULL;
}

int main(int argc, char **argv)
{
    int retval, i;

    pthread_t writer_id, reader_id;
    pthread_attr_t attr;
    int nreadercount = 1, nwritercount = 1;

    if (argc != 2) {
        fprintf(stderr, "usage, <%s threadcount>", argv[0]);
        return -1;
    }
    retval = pthread_rwlock_init(&rwlock, NULL);
    if (retval) {
        fprintf(stderr, "init lock failed\n");
        return retval;
    }
    pthread_attr_init(&attr);
    //pthread_attr_setdetachstate用來設定執行緒的分離狀態
    //也就是說一個執行緒怎麼樣終止自己,狀態設定為PTHREAD_CREATE_DETACHED
    //表示以分離狀態啟動執行緒
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

    //分別在main函式中對讀出者和寫入者加鎖,得到的處理結果是不一樣的
    pthread_rwlock_wrlock(&rwlock);
// pthread_rwlock_rdlock(&rwlock);
    for (i = 0; i < atoi(argv[1]); i++) {
        if (random() % 2) {
            pthread_create(&reader_id, &attr, readers, (void *)nreadercount);
            printf("create reader %d\n", nreadercount++);
        } else {
            pthread_create(&writer_id, &attr, writers, (void *)nwritercount);
            printf("create writer %d\n", nwritercount++);
        }
    }
    sleep(5);//sleep是為了等待另外的執行緒的執行
    printf("main unlock\n");
    pthread_rwlock_unlock(&rwlock);
    sleep(5);//sleep是為了等待另外的執行緒的執行
    return 0;
}

[[email protected] apue]# ./child_thr_sig 20
create reader 1
create writer 1
create reader 2
create reader 3
create reader 4
create reader 5
create writer 2
create writer 3
create reader 6
create reader 7
create writer 4
create reader 8
create writer 5
create reader 9
create reader 10
create writer 6
create writer 7
create writer 8
create writer 9
create writer 10
main unlock
writer 7 got the lock
writer 10 got the lock
writer 5 got the lock
writer 1 got the lock
writer 2 got the lock
writer 3 got the lock
writer 4 got the lock
writer 6 got the lock
writer 8 got the lock
writer 9 got the lock
reader 8 got the lock
reader 9 got the lock
reader 10 got the lock
reader 6 got the lock
reader 3 got the lock
reader 4 got the lock
reader 1 got the lock
reader 5 got the lock
reader 2 got the lock
reader 7 got the lock
/*************************************************************
 * pthread_rwlock_test2.c:驗證讀寫鎖的預設順序
 * 如果在main函式中用pthread_rwlock_wrlock上鎖,那麼
 * 如果所有執行緒都阻塞在寫鎖上的時候,優先處理的是被阻塞的寫鎖
 * 然後才處理讀出鎖
 * 如果在main函式中用pthread_rwlock_rdlock上鎖,那麼
 * 如果有讀者正在讀的時候即使後面到來的寫者比另外一些到來的讀者更早
 * 也是先處理完讀者,才轉而處理寫者,這樣會導致寫飢餓
 * 
 * 由此(執行結果)可以看出,LINUX平臺預設的是讀者優先,如果想要以寫者優先
 * 則需要做一些處理
 **************************************************************/
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>

pthread_rwlock_t rwlock;

void *readers(void *arg)
{
    pthread_rwlock_rdlock(&rwlock);
    printf("reader %d got the lock\n", (int)arg);
    pthread_rwlock_unlock(&rwlock);
    //return NULL;
}
void *writers(void *arg)
{
    pthread_rwlock_wrlock(&rwlock);
    printf("writer %d got the lock\n", (int)arg);
    pthread_rwlock_unlock(&rwlock);
    //return NULL;
}

int main(int argc, char **argv)
{
    int retval, i;

    pthread_t writer_id, reader_id;
    pthread_attr_t attr;
    int nreadercount = 1, nwritercount = 1;

    if (argc != 2) {
        fprintf(stderr, "usage, <%s threadcount>", argv[0]);
        return -1;
    }
    retval = pthread_rwlock_init(&rwlock, NULL);
    if (retval) {
        fprintf(stderr, "init lock failed\n");
        return retval;
    }
    pthread_attr_init(&attr);
    //pthread_attr_setdetachstate用來設定執行緒的分離狀態
    //也就是說一個執行緒怎麼樣終止自己,狀態設定為PTHREAD_CREATE_DETACHED
    //表示以分離狀態啟動執行緒
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

    //分別在main函式中對讀出者和寫入者加鎖,得到的處理結果是不一樣的
    // pthread_rwlock_wrlock(&rwlock);
    pthread_rwlock_rdlock(&rwlock);
    for (i = 0; i < atoi(argv[1]); i++) {
        if (random() % 2) {
            pthread_create(&reader_id, &attr, readers, (void *)nreadercount);
            printf("create reader %d\n", nreadercount++);
        } else {
            pthread_create(&writer_id, &attr, writers, (void *)nwritercount);
            printf("create writer %d\n", nwritercount++);
        }
    }
    sleep(5);//sleep是為了等待另外的執行緒的執行
    printf("main unlock\n");
    pthread_rwlock_unlock(&rwlock);
    sleep(5);//sleep是為了等待另外的執行緒的執行
    return 0;
}

[[email protected] apue]# ./child_thr_sig 20
create reader 1
create writer 1
create reader 2
create reader 3
create reader 4
create reader 5
create writer 2
create writer 3
create reader 6
create reader 7
create writer 4
create reader 8
create writer 5
create reader 9
create reader 10
create writer 6
create writer 7
create writer 8
create writer 9
create writer 10
reader 8 got the lock
reader 9 got the lock
reader 10 got the lock
reader 6 got the lock
reader 3 got the lock
reader 4 got the lock
reader 1 got the lock
reader 5 got the lock
reader 2 got the lock
reader 7 got the lock
main unlock
writer 7 got the lock
writer 10 got the lock
writer 5 got the lock
writer 1 got the lock
writer 2 got the lock
writer 3 got the lock
writer 4 got the lock
writer 6 got the lock
writer 8 got the lock
writer 9 got the lock
  • 使用讀寫鎖進行基本設計(線上程搜尋作業的頻率遠遠高於增加和刪除作業時,讀寫鎖才能改善效能)
#include <stdlib.h>
#include <pthread.h>

struct job {
    struct job *j_next;
    struct job *j_prev;
    pthread_t j_id; /* tells which thread handles this job */
    /* ... more stuff here ... */
};

struct queue {
    struct job *q_head;
    struct job *q_tail;
    pthread_rwlock_t q_lock;
};

/*
 * Initialize a queue.
 */
int
queue_init(struct queue *qp)
{
    int err;

    qp->q_head = NULL;
    qp->q_tail = NULL;
    err = pthread_rwlock_init(&qp->q_lock, NULL);
    if (err != 0)
        return(err);
    /* ... continue initialization ... */
    return(0);
}

/*
 * Insert a job at the head of the queue.
 *