先从github上可以把相应的Synchrobench代码下载下来https://github.com/gramoli/synchrobench
一.lazy-list代码重构的潜在需求:
第一个也是最明显的问题是内存回收问题。 比如在并发执行的过程中,如果只是一个节点b的逻辑移除,即使它不再存在于抽象集合中。 但实际上还是有联系的,只是一开始就达不到。 如下所示
这样,如果项目不是空闲的,随着时间的推移,它会导致内存激增。 因此分配给项目节点的内存被回收。
如果你让我去做,我会在最后收回记忆来坚持记忆。
解决方案1:垃圾收集
但是这个方法只来自 Java 和 C#,而不是 C/C++(至少官方)
解决方案 2:智能指针(Smart Pointers)
C++ 及更高版本中的智能指针(#include),它通过三个 API 扮演每个对象的垃圾收集角色和 RAII。 而如果我们只是调用API,就像一个黑匣子,我们只知道结果而不知道过程。
解决方案 3:特定领域的回收
并发数据结构:如惰性链表、无锁链表
二.lazy-list潜在的线程安全问题
条件变量设置不合理,加锁解锁过程没有设置休眠时间。 如果超时到期,它将无限期地等待。 (没有有效和有用的等待功能)。
使用全局共享变量容易增加不同任务或线程之间的耦合,也增加了引入bug的风险,所以应该尽量少使用全局共享变量。
节点是否标记与标记参考情况的功能设计是高度耦合的。 如果实际情况发生变化,修改起来会相当麻烦。 有效性检查和搜索功能是相同的。 插入和删除函数不考虑 while 循环中的异常。 如果中间出现错误,就会陷入死循环。
解决方案:
- 通过引用传递而不是传递指针
- 尽可能依赖构造函数、复制构造函数和析构函数
- 不能只使用堆栈分配
三.lazy-list 代码详解
- coupling.c
int lockc_delete(intset_l_t *set, val_t val) {
node_l_t *curr, *next;
int found;
//Lock the first two elements
LOCK(&set->head->lock);
curr = set->head;
LOCK(&curr->next->lock);
next = curr->next;
//如果下一个节点的值<当前值:解锁前一个,保留当前的所有权,并在循环中锁定下一个
while (next->val < val) {
UNLOCK(&curr->lock);
curr = next;
LOCK(&next->next->lock);
next = next->next;
}
// wait until next node's value == current value
found = (val == next->val);
// 'found' = 1, 然后找到下一个节点,同时解锁并删除它。最后,解锁它的下一个节点
if (found) {
curr->next = next->next;
UNLOCK(&next->lock);
node_delete_l(next);
UNLOCK(&curr->lock);
} else { // 'found' = 0, only unclock it and its next node
UNLOCK(&curr->lock);
UNLOCK(&next->lock);
}
//return whether delete next node
return found;
}
delete, find, and insert方法也差不多
- insert.c
//当 d->unit_tx == 2 时,返回 ((curr->val == val) && !is_marked_ref((long) curr->next)),否则,在获取元素副本之前Locking每个元素
int set_contains_l(intset_l_t *set, val_t val, int transactional)
{
if (transactional == 2) return parse_find(set, val);
else return lockc_find(set, val);
}
add和remove方法与此相差不大
3) linkedlist-lock.c
node_l_t *new_node_l(val_t val, node_l_t *next, int transactional){// INIT_LOCK and assign memory to new node
intset_l_t *set_new_l(){//insert node and set min and max for node
void node_delete_l(node_l_t *node) {//DESTROY_LOCK
void set_delete_l(intset_l_t *set){//DESTROY_LOCK and move current node to next
int set_size_l(intset_l_t *set){//compute length of nodes
- lazy.c
/*
* File:
* lazy.c
* Author(s):
* Vincent Gramoli <vincent.gramoli@epfl.ch>
* Description:
* Lazy linked list implementation of an integer set based on Heller et al. algorithm
* "A Lazy Concurrent List-Based Set Algorithm"
* S. Heller, M. Herlihy, V. Luchangco, M. Moir, W.N. Scherer III, N. Shavit
* p.3-16, OPODIS 2005
*
* Copyright (c) 2009-2010.
*
* lazy.c is part of Synchrobench
*
* Synchrobench is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2
* of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "lazy.h"
inline int is_marked_ref(long i) {
return (int) (i &= LONG_MIN+1);
}
inline long unset_mark(long i) {
i &= LONG_MAX-1;
return i;
}
inline long set_mark(long i) {
i = unset_mark(i);
i += 1;
return i;
}
inline node_l_t *get_unmarked_ref(node_l_t *n) {
return (node_l_t *) unset_mark((long) n);
}
inline node_l_t *get_marked_ref(node_l_t *n) {
return (node_l_t *) set_mark((long) n);
}
/*
* Checking that both curr and pred are both unmarked and that pred's next pointer
* points to curr to verify that the entries are adjacent and present in the list.
*/
inline int parse_validate(node_l_t *pred, node_l_t *curr) {
return (!is_marked_ref((long) pred->next) && !is_marked_ref((long) curr->next) && (pred->next == curr));
}
int parse_find(intset_l_t *set, val_t val) {//wait until curr->val == val, set next node to unmark
node_l_t *curr;
curr = set->head;
while (curr->val < val)
curr = get_unmarked_ref(curr->next);
return ((curr->val == val) && !is_marked_ref((long) curr->next));
}
int parse_insert(intset_l_t *set, val_t val) {
node_l_t *curr, *pred, *newnode;
int result, validated, notVal;
while (1) {
//Init
pred = set->head;
curr = get_unmarked_ref(pred->next);
//Unmark and lock each node
while (curr->val < val) {
pred = curr;
curr = get_unmarked_ref(curr->next);
}
LOCK(&pred->lock);
LOCK(&curr->lock);
//check its validated and curr->val whether equals val
validated = parse_validate(pred, curr);
notVal = (curr->val != val);
result = (validated && notVal);
//only meeting at same time, make pred->next = newnode
if (result) {
newnode = new_node_l(val, curr, 0);
pred->next = newnode;
}
//UNLOCK curr and pred
UNLOCK(&curr->lock);
UNLOCK(&pred->lock);
//wait until pred and curr meet the validate conditions, exit this loop
if(validated)
return result;
}
}
/*
* Logically remove an element by setting a mark bit to 1
* before removing it physically.
*
* NB. it is not safe to free the element after physical deletion as a
* pre-empted find operation may currently be parsing the element.
* TODO: must implement a stop-the-world garbage collector to correctly
* free the memory.
*/
int parse_delete(intset_l_t *set, val_t val) {
node_l_t *pred, *curr;
int result, validated, isVal;
while(1) {
//Init
pred = set->head;
curr = get_unmarked_ref(pred->next);
//Unmark and lock each node
while (curr->val < val) {
pred = curr;
curr = get_unmarked_ref(curr->next);
}
LOCK(&pred->lock);
LOCK(&curr->lock);
/*
* Mark Phase:
* Start from the root set to find all memory block references, and then mark the referenced memory block (mark)
*/
//check its validated and curr->val whether equals val
validated = parse_validate(pred, curr);
isVal = val == curr->val;
result = validated && isVal;
//only meeting at same time, make pred->next = newnode
if (result) {
curr->next = get_marked_ref(curr->next);
pred->next = get_unmarked_ref(curr->next);
}
//UNLOCK curr and pred
UNLOCK(&curr->lock);
UNLOCK(&pred->lock);
/*
* Sweep Phase:
* All memory blocks that are not marked are considered garbage and can be recycled
*/
//find memory blocks that are not marked, setting a mark bit to 1
result = result && parse_find(curr,isVal);
//wait until pred and curr meet the validate conditions, exit this loop
if(validated){
//free the element
free(curr);
return result;
}
}
}
//Checking this node whether null
bool isEmpty(intset_l_t *set, val_t val) {
node_l_t *curr = set->head;
//search an element that exist in the list, both logically and physically
while (curr->val != val) {
//make sure element was not removed logically
curr = get_unmarked_ref(curr->next);
}
return ((curr->val == val) && !is_marked_ref((long) curr->next));
}
/*Table of sleeping threads. */
static struct array *sleepers;
struct node_l_t *thread_bootstrap(void){
node_l_t *curr, *pred, *newnode;
/*Create the data structures we need.*/
sleepers = array_create();
if(!sleepers){
printf("Cannot create sleepers array\n");
}
}
void node_sleep(intset_l_t *set, val_t val){
//may not sleep in an interrupt handler
node_l_t *curr, *pred;
LOCK(&pred->lock);
LOCK(&curr->lock);
mi_switch(S_SLEEP);
UNLOCK(&curr->lock);
UNLOCK(&pred->lock);
}
/* Release lock, put node to sleep until cv is signaled; when node wakes up again, re-acquire lock before returning */
void node_wakeup(const void *addr){
int i, result;
isVal = val == curr->val;
node_l_t *curr;
//This is inefficient, Feel free to improve it.
for(i=0; i<array_getnum(sleepers); i++){
node_l_t *n = array_getguy(sleepers, i);
if (n->val == val) {
//Remove from list
array_remove(sleepers. i);
//must look at the same sleepers[i] again
i--;
UNLOCK(&curr->lock);
curr = curr->next;
/*
Because we preallocate during therad_fork,
this should never fail.
*/
result = make_runnable(t);
assert(result==0);
}
}
}
- lazy.h
/*
* File:
* lazy.c
* Author(s):
* Vincent Gramoli <vincent.gramoli@epfl.ch>
* Description:
* Lazy linked list implementation of an integer set based on Heller et al. algorithm
* "A Lazy Concurrent List-Based Set Algorithm"
* S. Heller, M. Herlihy, V. Luchangco, M. Moir, W.N. Scherer III, N. Shavit
* p.3-16, OPODIS 2005
*
* Copyright (c) 2009-2010.
*
* lazy.c is part of Synchrobench
*
* Synchrobench is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2
* of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "coupling.h"
/* handling logical deletion flag */
inline int is_marked_ref(long i);
inline long unset_mark(long i);
inline long set_mark(long i);
inline node_l_t *get_unmarked_ref(node_l_t *n);
inline node_l_t *get_marked_ref(node_l_t *n);
/* linked list accesses */
int parse_validate(node_l_t *pred, node_l_t *curr);
int parse_find(intset_l_t *set, val_t val);
int parse_insert(intset_l_t *set, val_t val);
int parse_delete(intset_l_t *set, val_t val);