这次回顾第12章部分习题。

电子书地址:

http://eol.bnuz.edu.cn/meol/common/script/preview/download_preview.jsp?fileid=2169600&resid=242120&lid=28605

参考资料:

https://dreamanddead.github.io/CSAPP-3e-Solutions/chapter12/

说明:

19~20是难题,21,23,26以及31之后习题略过。

12.16

代码:

#include "csapp.h"
void *thread(void *vargp);                    //line:conc:hello:prototype

int main(int argc, char **argv)                                    //line:conc:hello:main
{
    int n = -1;
    if (argc != 2) {
        fprintf(stderr, "you should enter a positive number!\n");
        exit(0);
    }
    n = atoi(argv[1]);
    if (n == -1) {
        fprintf(stderr, "you should enter a positive number!\n");
        exit(0);
    }

    pthread_t tid;                            //line:conc:hello:tid
    for (int i = 0; i < n; i++) {
        Pthread_create(&tid, NULL, thread, NULL); //line:conc:hello:create
        Pthread_join(tid, NULL);                  //line:conc:hello:join
    }

    exit(0);                                  //line:conc:hello:exit
}

void *thread(void *vargp) /* thread routine */  //line:conc:hello:beginthread
{
    printf("Hello, world!\n");                 
    return NULL;                               //line:conc:hello:return
}                                              //line:conc:hello:endthread

编译:

gcc -o 16 16.c -lpthread

运行:

$ ./16 3
Hello, world!
Hello, world!
Hello, world!

12.17

A

主线程没有等待对等线程终止。

B

代码:

/* 
 * hellobug.c - "hello, world" program with a bug
 */
/* $begin hellobug */
/* WARNING: This code is buggy! */
#include "csapp.h"
void *thread(void *vargp);

int main() 
{
    pthread_t tid;

    Pthread_create(&tid, NULL, thread, NULL);
    // add
    Pthread_join(tid, NULL);
    exit(0); //line:conc:hellobug:exit
}

/* Thread routine */
void *thread(void *vargp) 
{
    Sleep(1);
    printf("Hello, world!\n"); 
    return NULL;
}
/* $end hellobug */

编译:

gcc -o 17 17.c -lpthread

运行:

$ ./17
Hello, world!

12.18

A

$H_{2}, L_{2}, U_{2}, H_{1}, L_{1}, S_{2}, U_{1}, S_{1}, T_{1}, T_{2}$

不安全。

B

$H_{2}, H_{1}, L_{1}, U_{1}, S_{1}, L_{2}, T_{1}, U_{2}, S_{2}, T_{2}$

安全。

C

$H_{1}, L_{1}, H_{2}, L_{2}, U_{2}, S_{2}, U_{1}, S_{1}, T_{1}, T_{2}$

不安全。

说明:19~21比较难,目前还没完全理解。

12.19

给writer增加自己的锁wg,这样在V(&wg)执行之后,等候的reader就可以执行,因为此时wg为1。

代码:

/* 
 * Readers-writers solution with weak reader priority
 * From Courtois et al, CACM, 1971.
*/
#include "csapp.h"

/* $begin reader1 */
/* Global variables */
int readcnt;    /* Initially = 0 */
sem_t mutex, w, wg; /* Both initially = 1 */

void reader(void) 
{
    while (1) {
	P(&mutex);
	readcnt++;
	if (readcnt == 1) /* First in */
	    P(&w);          
	V(&mutex);          

	/* Critical section */
	/* Reading happens  */

	P(&mutex);
	readcnt--;
	if (readcnt == 0) /* Last out */
	    V(&w);
	V(&mutex);
    }
}
/* $end reader1 */

/* $begin writer1 */
void writer(void) 
{
    while (1) {
    P(&wg);
	P(&w);

	/* Critical section */
	/* Writing happens  */ 

	V(&w);
    V(&wg);
    }
}
/* $end writer1 */

12.20

代码:

#include "csapp.h"

#define N 10

/* $begin reader1 */
/* Global variables */
sem_t cnt;    /* Initially = N */
sem_t mutex; /* Both initially = 1 */

void reader(void) 
{
    while (1) {
        P(&cnt);

        /* Critical section */
        /* Reading happens  */

        V(&cnt);
    }
}
/* $end reader1 */

/* $begin writer1 */
void writer(void) 
{
    while (1) {
        P(&mutex);
        // 只有当reader全部结束时才能执行完循环, 循环结束时cnt = 0
        for (int i = 0; i < N; i++) {
            P(&cnt);
        }
        V(&mutex);
        
        /* Critical section */
        /* Writing happens  */ 
        for (int i = 0; i < N; i++) {
            V(&cnt);
        }

        // 此时mutex = 1, cnt = N, reader和writer谁先执行的概率相同
    }
}
/* $end writer1 */

12.22

思路:看到\n就返回。

代码:

/* $begin select */
#include "csapp.h"
void echo(int connfd);
void command(void);

int main(int argc, char **argv) 
{
    int listenfd, connfd;
    socklen_t clientlen;
    struct sockaddr_storage clientaddr;
    fd_set read_set, ready_set;

    if (argc != 2) {
        fprintf(stderr, "usage: %s <port>\n", argv[0]);
        exit(0);
    }
    listenfd = Open_listenfd(argv[1]);  //line:conc:select:openlistenfd

    FD_ZERO(&read_set);              /* Clear read set */ //line:conc:select:clearreadset
    FD_SET(STDIN_FILENO, &read_set); /* Add stdin to read set */ //line:conc:select:addstdin
    FD_SET(listenfd, &read_set);     /* Add listenfd to read set */ //line:conc:select:addlistenfd

    while (1) {
        ready_set = read_set;
        Select(listenfd+1, &ready_set, NULL, NULL, NULL); //line:conc:select:select
        if (FD_ISSET(STDIN_FILENO, &ready_set)) //line:conc:select:stdinready
            command(); /* Read command line from stdin */
        if (FD_ISSET(listenfd, &ready_set)) { //line:conc:select:listenfdready
            clientlen = sizeof(struct sockaddr_storage); 
            connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
            echo(connfd); /* Echo client input until EOF */
            Close(connfd);
        }
    }
}

void command(void) {
    char buf[MAXLINE];
    if (!Fgets(buf, MAXLINE, stdin))
	    exit(0); /* EOF */
    printf("%s", buf); /* Process the input command */
}
/* $end select */


void echo(int connfd) 
{
    int n; 
    char buf[MAXLINE]; 
    rio_t rio;

    Rio_readinitb(&rio, connfd);
    while((n = Rio_readlineb(&rio, buf, 1)) != 0) {
        Rio_writen(connfd, buf, n);
        // add
        if (buf[0] == '\n') {
            return;
        }
    }
}

12.24

代码:

ssize_t Rio_readn(int fd, void *ptr, size_t nbytes) 
{
    ssize_t n;
  
    if ((n = rio_readn(fd, ptr, nbytes)) < 0)
	unix_error("Rio_readn error");
    return n;
}

void Rio_writen(int fd, void *usrbuf, size_t n) 
{
    if (rio_writen(fd, usrbuf, n) != n)
	unix_error("Rio_writen error");
}

void Rio_readinitb(rio_t *rp, int fd)
{
    rio_readinitb(rp, fd);
} 

ssize_t Rio_readnb(rio_t *rp, void *usrbuf, size_t n) 
{
    ssize_t rc;

    if ((rc = rio_readnb(rp, usrbuf, n)) < 0)
	unix_error("Rio_readnb error");
    return rc;
}

ssize_t Rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen) 
{
    ssize_t rc;

    if ((rc = rio_readlineb(rp, usrbuf, maxlen)) < 0)
	unix_error("Rio_readlineb error");
    return rc;
} 

都是隐式可重入的。

12.25

代码:

void echo_cnt(int connfd) 
{
    int n; 
    char buf[MAXLINE]; 
    rio_t rio;
    static pthread_once_t once = PTHREAD_ONCE_INIT;

    Pthread_once(&once, init_echo_cnt); //line:conc:pre:pthreadonce
    Rio_readinitb(&rio, connfd);        //line:conc:pre:rioinitb
    while((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0) {
	P(&mutex);
	byte_cnt += n; //line:conc:pre:cntaccess1
	printf("server received %d (%d total) bytes on fd %d\n", 
	       n, byte_cnt, connfd); //line:conc:pre:cntaccess2
	V(&mutex);
	Rio_writen(connfd, buf, n);
    }
}
/* $end echo_cnt */

说明:

  • 线程安全的,因为调用了$P,V$;
  • 不是可重入的,因为调用了静态变量mutex;

12.27

如果在执行

fpout = fdopen(sockfd, "w");

之后创建并发流,那么可能会执行多次

fclose(fpin);
fclose(fpout);

从而产生问题。

12.28

都不会死锁。

A

图示:

B

图示:

C

图示:

D

图示:

12.29

不会死锁。

a vs b:

a vs c:

b vs c:

12.30

A

利用代码模拟结果:

import copy

one = [-1, -2, 2, -3, 3, 1]
two = [-3, -2, 2, 3, -1, 1]
three = [-3, 3, -2, -1, 1, 2]
n = len(one)
actions = [one, two, three]
state = [0, 1, 1, 1]
index = [0, 0, 0]

# 标记是否访问过状态
flag = [[[0 for i in range(n + 1)] for j in range(n + 1)] for k in range(n + 1)]
# 将数字转为字母
num2char = ['', 'a', 'b', 'c']

def judge(state):
    res = [0, 0, 0, 0]
    for i in range(1, 4):
        if state[i] < 0:
            res[i] += 1

    return res

def print_res(res, action):
    if res != [0, 0, 0, 0]:
        sen = f"thread"
        for i in range(3):
            # 动作i为P, 并且对应的结果为负数
            if action[i] < 0 and res[i + 1]:
                sen += f" {i + 1}"
        sen += ","
        cnt = 0
        for i in range(1, 4):
            if res[i]:
                cnt += 1
                sen += f" {num2char[i]}"
        if (cnt > 1):
            print(sen)

def update(state, action, i):
    index = abs(action[i])
    delta = 1
    if (action[i] < 0):
        delta = -1
    state[index] += delta

def dfs(state, actions, index, n):
    tmp = True
    for i in range(3):
        if index[i] != n:
            tmp = False
            break
    if tmp:
        return

    if flag[index[0]][index[1]][index[2]]:
        return
    flag[index[0]][index[1]][index[2]] = 1

    action = [0, 0, 0]
    for j in range(3):
        i = index[j]
        if (i > 0 and actions[j][i - 1] < 0):
            action[j] = -1
    print_res(judge(state), action)

    for i in range(3):
        if index[i] < n:
            old_state = copy.deepcopy(state)
            update(state, actions[i], index[i])
            index[i] += 1
            dfs(state, actions, index, n)
            index[i] -= 1
            state = copy.deepcopy(old_state)

dfs(state, actions, index, n)

实验结果:

thread 2, a b
thread 1 3, a c    
thread 1 3, a c    
thread 2 3, b c    
thread 1 2 3, a b c
thread 2, b c      
thread 1 3, a c
thread 2, a b
thread 1, a b
thread 1 2, a b
thread 1 2, a b
thread 1 2, a b
thread 1, a b
thread 1, a b
thread 2 3, b c
thread 1 2, a b
thread 1 2, a b
thread 1, a b
thread 1 2, a b

线程1占用的互斥锁:

a和c, a和b。

线程2占用的互斥锁:

a和b, b和c。

线程3占用的互斥锁:

a和c, b和c。

B

线程2,3,因为违反了顺序。

C

加锁顺序相同即可。