# linux2021: ilkclord
> [quiz-link](https://hackmd.io/@sysprog/linux2021-summer-quiz)
## $\delta$ - 1
This program is a mpmc model , and the number of consumer and producer are set to be 4 .
The pop and push action are defined as
* `pop` : pop out the elements from the top of the linked list
* `push` : push an elements into the tail of the linked list
The program starts by
1 . Initialize the queue
2 . Creating the consumer and producer thread
3 . Start poping and pushing
4 . Send the kill signal to end the program
### Thread Safety
The program assures the safety of data by using a queue with two `mutex_lock`
```clike
typedef struct { /* Two lock queue */
node_t *first, *last;
mtx_t *first_mutex, *last_mutex;
} con_queue_t;
```
`first_mutex` : protect the `first`
`last_mutex` : protect the `last`
* **poping**
`con_pop` take use of `first` to do the pop action .It has to assure the `first` is only used by sigle thread at the same time , so it lock the block which needs to use `first`
```clike
mtx_lock(queue->first_mutex);
node_t *node = queue->first; /* Node to be removed */
node_t *new_header = queue->first->next; /* become the first in the queue */
/* Queue is empty */
if (!new_header) {
mtx_unlock(queue->first_mutex);
return NULL;
}
/* Queue not empty: retrieve data and rewire */
void *return_value = node->value;
queue->first = new_header;
mtx_unlock(queue->first_mutex);
```
* **pushing**
`con_push` takes use of `last` to do the push , and it assures `last` in a similar way . To keep the safety , a `mutex_lock` is applied to lock the block that needs to use `last`.
```clike
mtx_lock(queue->last_mutex);
if(queue->last) queue->last->next = node ;
queue->last = node;
mtx_unlock(queue->last_mutex);
```
### Thread Funtion
#### push_thread
This thread push elements into the queue . The thread terminate when it has push the limited number of elments in .
```clike=
int push_thread(void *queue_ptr)
{
con_queue_t *queue = (con_queue_t *) queue_ptr;
/* Push ints into queue */
for (int i = 0; i < NUM; ++i) {
int *pushed_value = malloc(sizeof(int));
*pushed_value = i;
if (con_push(queue, pushed_value) != Q_OK)
printf("Error pushing element %i\n", i);
}
thrd_exit(0);
}
```
#### pop_thread
This thread keeps poping out the elements from the queue , and it also free the elements it popped out .The thread will terminate when it pops out the element with value -1 , which is a killed signal .
```clike=
int pop_thread(void *queue_ptr)
{
con_queue_t *queue = (con_queue_t *) queue_ptr;
/* Read values from queue. Break loop on -1 */
while (1) {
int *popped_value = con_pop(queue);
if (popped_value) {
if (*popped_value == -1) {
free(popped_value);
break;
}
free(popped_value);
}
}
thrd_exit(0);
}
```
### Flow Chart
* program
```graphviz
Digraph G{
Initilaize->"create threads" ->pushing ->terminate ->"Send killing signal" ->End
"create threads" ->popping->End
}
```
* queue
```graphviz
Digraph G{
push->last
first->"node1"->"node2"->"node3"->"..."-> last
first->pop
rankdir = LR
}
```
### Problem
After the testing , we find that thhere is possibility that the program will be stock in a loop , which is the while_loop in the `pop_thread` .
Browse the code , the problem may be in `cond_pop`
`cond_pop` :
```clike=
void *con_pop(con_queue_t *queue)
{
mtx_lock(queue->first_mutex);
node_t *node = queue->first; /* Node to be removed */
node_t *new_header = queue->first->next; /* become the first in the queue */
/* Queue is empty */
if (!new_header) {
mtx_unlock(queue->first_mutex);
return NULL;
}
/* Queue not empty: retrieve data and rewire */
void *return_value = node->value;
queue->first = new_header;
mtx_unlock(queue->first_mutex);
/* Free removed node and return */
free(node);
return return_value;
}
```
In line 9 , the if gate block the empty queue , but it checks the `!new_header` instead of `!node` , this makes the queue will alwalys has at least 1 elements inside . For the last one data in the queue , it will never be popped out , so we can changed the the check condition to `!node` .
### Improvement
In line 9 , the if gate block the empty queue , but it checks the `!new_header` instead of `!node` , this makes the queue will alwalys has at least 1 elements inside .For the last one data in the queue , it will never be popped out .
I guess the reason for this design is to keep the connection between `first` and `last` .
#### Solution 1
Add one more condition to check if killing signal has received .
```clike
if (!new_header && *(int *)(node->value) != -1 ) {
mtx_unlock(queue->first_mutex);
return NULL;
}
```
#### Solution 2
We can changed the check condition to `!node` ,too .But there exists some bugs . I analyze the behavoirs of `first` and `last` .
Initialize of `last` and `first`
```clike
node_t *dummy = _con_node_init(NULL);
if (!dummy) {
con_free(queue);
return NULL;
}
queue->first = queue->last = dummy;
```
`first` and `last`points to a same node `dummy ` .In expected situation , `last` will grows the queue bigger and indicates the tail ,`first` pops out the elements and decrease the size of queue .
Consider the special case , the elements were all popped out ,so `first` and `last` points to `NULL` . This is a serious problem , since `first` and `last` no longer has connection between each other , the pop function can't pop the elements out .
Expected :
```graphviz
Digraph G{
first->list->last
rankdir = LR
}
```
Broken :
```graphviz
Digraph G{
first
list->last
rankdir = LR
}
```
I add the modificatoin below to reconnect `first` and `last` and assure the correctness .
* `con_push`
I use if_else to connect `first` and `last` when pushing into empty queue ,also assure the thread safety .
```clike=
if(queue->last != NULL)
queue->last->next = node ;//AAA
else{
mtx_lock(queue->first_mutex) ;
queue->first = node ;
mtx_unlock(queue->first_mutex) ;
}
```
* `con_pop`
I switch the order below , to check the if `first` is `NULL` , and return `NULL` if statement is true .
```clike=
node_t *node = queue->first; /* Node to be removed */
if (!node) {
mtx_unlock(queue->first_mutex);
return NULL;
}
node_t *new_header = queue->first->next; /* become the first in the queue */
```
## $\delta$ - 2
To modify the program to a lock free program , I replace the `mutex_lock` by C11 atomic operation .
### C11 Atomic
The only two change is in `con_pop` and `con_push`
* `con_pop`
```clike
void *con_pop(con_queue_t *queue)
{
node_t *node = atomic_load(&(queue->first));
/*Empty queue*/
if(!node->next && *(int *)(node->value)!= -1)
return NULL ;
/*test if the node we fetched isn't use by others
* if isn't , modified it to next one
* */
if(!atomic_compare_exchange_strong(&(queue->first) , &node , node->next))
return NULL ;
void *return_value = node->value ;// BBB;
/* Free removed node and return */
free(node);
return return_value;
}
```
We applied atomic operation by first fetching the value of `first` . Then we check if `first` has been modified before we record the `return_value` . If it has been modified , it is unsafe operation , we return `NULL`
* `con_push`
```clike
int con_push(con_queue_t *restrict queue, void *restrict new_element)
{
/* Prepare new node */
node_t *node = _con_node_init(new_element);
if (!node)
return Q_ERROR;
while(1){
node_t *tmp = atomic_load(&(queue->last)) ;
if(atomic_compare_exchange_strong(&(queue->last) , &tmp , node)){
atomic_store(&(tmp->next) , node) ;
break ;
}
}
return Q_OK;
}
```
We applied it in a similar way , but we let it try untill it successed . Because in the original `con_push` we return `Q_ERROR` only if the `malloc` failed , so we repeat this operation untill sucess .