qemu多线程技术的实现

1 qemy_mutex_*

  qemu_mutex_init –> pthread_mutex_init、qemu_mutex_destroy –> pthread_mutex_destroy、qemu_mutex_lock –> pthread_mutex_lock(如果锁被占据,则阻塞当前线程)、qemu_mutex_trylock –> pthread_mutex_trylock(不会阻塞当前线程,会立即返回一个锁的状态值)、qemu_mutex_unlock –> pthread_mutex_unlock

1.1 互斥锁属性

  互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。
  当前(glibc2.2.3,linuxthreads0.9)有四个值可供选择:
    * PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
    * PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
    * PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
    *PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。

1.2 锁操作

  主要包括加锁pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁 pthread_mutex_trylock()三个,不论哪种类型的锁,都不可能被两个不同的线程同时得到,而必须等待解锁。

1.3 实例代码

#include<stdio.h>
#include<pthread.h>
#include<string.h>
typedef struct{
        int count;
        pthread_mutex_t mutex;
}counter;
void reader(void *arg);
void writer(void *arg);
#define writer_c 10
#define reader_c 20
#define total_c writer_c + reader_c
int main(int argc, char *argv[]){
        counter C;
        C.count = 0;
        srand(time(NULL));
        pthread_mutex_init(&(C.mutex), NULL);
        pthread_t pt[total_c];
        int ret, i;
        for(i = 0; i < reader_c; i ++){
                ret = pthread_create(&pt[i], NULL, (void *)reader, (void *)&C);
                if(ret){
                        printf("reader %d create error!\n", i + 1);
                        return -1;
                }
        }
        for(i = 0; i < writer_c; i ++){
                ret = pthread_create(&pt[reader_c + i], NULL, (void *)writer, (void *)&C);
                if(ret){
                        printf("writer %d create error!\n", i + 1);
                        return -1;
                }
        }
        for(i = 0; i < total_c; i ++){
                pthread_join(pt[i], NULL);
        }
        return 0;
}
void reader(void *arg){
        counter *c = arg;
        while(1){
                pthread_mutex_lock(&(c->mutex));
                if(c->count > 0){
                        c->count --;
                        printf("reader-%lu: (c->count --) = %d\n", pthread_self(), c->count);
                }else{
                        printf("reader-%lu: get nothing!\n", pthread_self());
                }
                pthread_mutex_unlock(&(c->mutex));
                sleep(rand() % 10);
        }
}
void writer(void *arg){
        counter *c = arg;
        while(1){
                pthread_mutex_lock(&(c->mutex));
                c->count ++;
                printf("writer-%lu: (c->count ++)= %d\n", pthread_self(), c->count);
                pthread_mutex_unlock(&(c->mutex));
                sleep(rand() % 6);
        }
}

1.4 缺陷

  使用锁机制可以满足线程之间的互斥关系,但是还存在线程同步关系,比如:reader线程和writer线程之间存在同步关系,即:当writer线程产生一个资源(c->count ++)之后,reader线程便可以立即取走该资源(c->count –),因此可以立即唤醒一个被阻塞的reader线程,如果继续等待系统调度一个被阻塞的reader线程或者writer线程的话,便会造成效率低。条件变量是一种通过信号机制和锁机制来解决上述线程同步关系,大致的原理为:当一个线程获得了被锁机制控制的临界资源,但是没有达到要执行的条件(c->count > 0),该线程可以使用pthread_cond_wait(…)在该条件变量上等待,一旦有信号到达,该线程便会被唤醒继续执行。其中pthread_cond_wait(…)会自动释放线程获得的锁,然后等待条件变量的变化,一旦被唤醒,立即自动恢复该线程对于锁的获取,然后返回。

2 qemu_cond_*

  qemu_cond_init –> pthread_cond_init、qemu_cond_destroy –> pthread_cond_destroy、qemu_cond_signal –> pthread_cond_signal(唤醒一个等待某个条件变量的线程)、qemu_cond_broadcast –> pthread_cond_broadcast(唤醒所有在该条件变量上等待的线程)、qemu_cond_wait –> pthread_cond_wait (该函数需要在获得锁之后进行调用,否则可能会出现死锁)、pthread_cond_timedwait用于等待一定时间的条件变量。

2.1 注意事项

(1)pthread_cond_wait运行前加锁、运行后释放锁

qemu_mutex_lock
…
pthread_cond_wait
…
qemu_mutex_unlock

(2)pthread_cond_signal或者pthread_cond_broadcast与锁的顺序关系有两种方式
  方式一:

qemu_mutex_lock
…
pthread_cond_signal / pthread_cond_broadcast
…
qemu_mutex_unlock

  优缺点:在某些线程的实现中,会造成等待线程从内核中唤醒(由于pthread_cond_signal)然后又回到内核空间(因为pthread_cond_signal返回后会有原子加锁的 行为),所以一来一回会有性能的问题。但是在Linux Threads或者NPTL里面,就不会有这个问题,因为在Linux 线程中,有两个队列,分别是cond_wait队列和mutex_lock队列,pthread_cond_signal只是让线程从cond_wait队列移到mutex_lock队列,而不用返回到用户空间,不会有性能的损耗。所以在Linux中推荐使用这种模式。
  方式二:

qemu_mutex_lock
…
qemu_mutex_unlock
pthread_cond_signal / pthread_cond_broadcast

  优缺点:不会出现上述潜在的性能损耗,因为在pthread_cond_signal之前就已经释放锁了。如果qemu_mutex_unlock和pthread_cond_signal之前,有个低优先级的线程正在mutex上等待的话,那么当qemu_mutex_unlock发生之后,这个低优先级的线程可能会立即抢占高优先级的线程(qemu_cond_wait的线程),而这在上面的放中间的模式下是不会出现的。

2.2 示例代码

#include<stdio.h>
#include<pthread.h>
#include<string.h>
typedef struct{
        int count;
        pthread_mutex_t mutex;
        pthread_cond_t cond;
}counter;
void reader(void *arg);
void writer(void *arg);
#define writer_c 10
#define reader_c 20
#define total_c writer_c + reader_c
int main(int argc, char *argv[]){
        counter C;
        C.count = 10;
        srand(time(NULL));
        pthread_mutex_init(&(C.mutex), NULL);
        pthread_cond_init(&(C.cond), NULL);
        pthread_t pt[total_c];
        int ret, i;
        for(i = 0; i < reader_c; i ++){
                ret = pthread_create(&pt[i], NULL, (void *)reader, (void *)&C);
                if(ret){
                        printf("reader %d create error!\n", i + 1);
                        return -1;
                }
        }
        for(i = 0; i < writer_c; i ++){
                ret = pthread_create(&pt[reader_c + i], NULL, (void *)writer, (void *)&C);
                if(ret){
                        printf("writer %d create error!\n", i + 1);
                        return -1;
                }
        }
        for(i = 0; i < total_c; i ++){
                pthread_join(pt[i], NULL);
        }
        pthread_mutex_destroy(&(C.mutex));
        pthread_cond_destroy(&(C.cond));
        return 0;
}
void reader(void *arg){
        counter *c = arg;
        while(1){
                pthread_mutex_lock(&(c->mutex));
                while(c->count <= 0){
                        pthread_cond_wait(&(c->cond), &(c->mutex));
                }
                c->count --;
                printf("reader-%lu: (c->count --) = %d\n", pthread_self(), c->count);
                pthread_mutex_unlock(&(c->mutex));
                sleep(rand() % 10);
        }
}
void writer(void *arg){
        counter *c = arg;
        while(1){
                pthread_mutex_lock(&(c->mutex));
                c->count ++;
                printf("writer-%lu: (c->count ++)= %d\n", pthread_self(), c->count);
                pthread_cond_signal(&(c->cond));
                pthread_mutex_unlock(&(c->mutex));
                //pthread_cond_signal(&(c->cond));
                sleep(rand() % 6);
        }
}   

3 qemu_sem_*

  qemu_sem_init –> sem_init(等价于pthread_mutex_init + pthread_cond_init)。
  qemu_sem_destroy –> sem_destroy(等价于pthread_cond_destroy + pthread_mutex_destroy)。
  qemu_sem_post –> sem_post(等价于pthread_mutex_lock + pthread_cond_signal + pthread_mutex_unlock)。
  qemu_sem_timedwait –> sem_trywait / sem_timedwait(等价于pthread_mutex_lock + pthread_cond_timedwait + pthread_mutex_unlock)。
  qemu_sem_wait –> sem_wait(等价于pthread_mutex_lock + pthread_cond_wait + pthread_mutex_unlock)。
  sem_trywait为sem_wait()的非阻塞版,如果信号量计数大于0,则信号量立即减1并返回0,否则立即返回-1。
  sem_getvalue(sem_t * sem, int * sval)读取sem中信号量计数,存于sval中,并返回0。

3.1 示例代码

#include<stdio.h>
#include<pthread.h>
#include<semaphore.h>
typedef struct{
        sem_t sem;
}counter;
void reader(void *arg);
void writer(void *arg);
#define writer_c 10
#define reader_c 20
#define total_c writer_c + reader_c
int main(int argc, char *argv[]){
        counter C;
        srand(time(NULL));
        sem_init(&(C.sem), 0, 5);
        pthread_t pt[total_c];
        int ret, i;
        for(i = 0; i < reader_c; i ++){
                ret = pthread_create(&pt[i], NULL, (void *)reader, (void *)&C);
                if(ret){
                        printf("reader %d create error!\n", i + 1);
                        return -1;
                }
        }
        for(i = 0; i < writer_c; i ++){
                ret = pthread_create(&pt[reader_c + i], NULL, (void *)writer, (void *)&C);
                if(ret){
                        printf("writer %d create error!\n", i + 1);
                        return -1;
                }
        }
        for(i = 0; i < total_c; i ++){
                pthread_join(pt[i], NULL);
        }
        sem_destroy(&(C.sem));
        return 0;
}
void reader(void *arg){
        counter *c = arg;
        while(1){
                while(sem_wait(&(c->sem)) < 0){};
                int count;
                if(sem_getvalue(&(c->sem), &count) < 0){
                        printf("reader: sem_getvalue error!\n");
                        break;
                }
                printf("reader-%lu: (count --) = %d\n", pthread_self(), count);
                sleep(rand() % 10);
        }
}
void writer(void *arg){
        counter *c = arg;
        while(1){
                if(sem_post(&(c->sem)) < 0){
                        printf("sem_post error!\n");
                        break;
                }
                int count;
                if(sem_getvalue(&(c->sem), &count) < 0){
                        printf("writer: sem_getvalue error!\n");
                        break;
                }
                printf("writer-%lu: (count ++)= %d\n", pthread_self(), count);
                sleep(rand() % 6);
        }
}

4 qemu_thread_*

4.1 工作原理

qemu_thread_atexit_add调用pthread_getspecific得到exit_key键对应的值NotifierList链表,然后向该链表中添加一个新的Notifier结构体变量,最后调用pthread_setspecific重新将整个NotifierList链表设置为exit_key键对应的值。
qemu_thread_atexit_remove调用pthread_getspecific得到exit_key键对应的值NotifierList链表,然后删除该链表的一个Notifier结构体元素,最后调用pthread_setspecific重新将整个NotifierList链表设置为exit_key键对应的值。
pthread_getpecific和pthread_setspecific实现同一个线程中不同函数之间的数据共享。
qemu_thread_atexit_run调用函数notifier_list_notify来遍历NotifierList链表中的每一个Notifier结构体元素,然后依次执行每个元素中注册的notify函数。
pthread_key_create用来创建线程私有数据。该函数从 TSD (Thread-specific Data)池中分配一项,将其地址值赋给 key 供以后访问使用。该函数原型为int pthread_key_create(pthread_key_t key, void (*destr_function) (void)),第 2 个参数是一个销毁函数,它是可选的,可以为 NULL,为 NULL 时,则系统调用默认的销毁函数进行相关的数据注销。如果不为空,则在线程退出时(调用 pthread_exit() 函数)时将以 key 锁关联的数据作为参数调用它,以释放分配的缓冲区、关闭文件流等。
qemu_thread_create –> pthread_attr_init –> pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) –> sigfillset(&set) –> pthread_sigmask(SIG_SETMASK, &set, &oldset) –> pthread_create(&thread->thread, &attr, start_routine, arg) –> pthread_sigmask(SIG_SETMASK, &oldset, NULL) –> pthread_attr_destroy(&attr)。
qemu_thread_get_self –> pthread_self、qemu_thread_is_self –> pthread_equal(pthread_self(), thread->thread)、qemu_thread_exit –> pthread_exit、qemu_thread_join –> pthread_join。

4.2 示例代码

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>

typedef struct{
        sem_t sem;
}counter;

void reader(void *arg);
void writer(void *arg);

pthread_key_t key;

#define writer_c 1
#define reader_c 1
#define total_c writer_c + reader_c

int main(int argc, char *argv[]){
        counter C;
        sem_init(&(C.sem), 0, reader_c);
        pthread_t pt[total_c];
        srand(time(NULL));
        if(pthread_key_create(&key, free)){
                printf("pthread_key_create error!\n");
                return -1;
        }
        int ret, i;
        for(i = 0; i < reader_c; i ++){
                ret = pthread_create(&pt[i], NULL, (void *)reader, (void *)&C);
                if(ret){
                        printf("reader %d create error!\n", i + 1);
                        return -1;
                }
        }
        for(i = 0; i < writer_c; i ++){
                ret = pthread_create(&pt[reader_c + i], NULL, (void *)writer, (void *)&C);
                if(ret){
                        printf("writer %d create error!\n", i + 1);
                        return -1;
                }
        }
        for(i = 0; i < total_c; i ++){
                pthread_join(pt[i], NULL);
        }
        sem_destroy(&(C.sem));
        return 0;
}

void reader(void *arg){
        counter *c = arg;
        int *p = (int *)malloc(4);
        *p = 1;
        pthread_setspecific(key, p);
        int t = 11;//initial sem->count = reader_c;
        while(t --){
                while(sem_wait(&(c->sem)) < 0){};
                int count;
                if(sem_getvalue(&(c->sem), &count) < 0){
                        printf("reader: sem_getvalue error!\n");
                        break;
                }
                printf("reader-%lu: (count --) = %d(key = %d)\n", pthread_self(), count, *(int *)pthread_getspecific(key));
        }
}

void writer(void *arg){
        counter *c = arg;
        int *p = (int *)malloc(4);
        *p = 2;
        pthread_setspecific(key, p);
        int t = 10;
        while(t --){
                if(sem_post(&(c->sem)) < 0){
                        printf("sem_post error!\n");
                        break;
                }
                int count;
                if(sem_getvalue(&(c->sem), &count) < 0){
                        printf("writer: sem_getvalue error!\n");
                        break;
                }
                printf("writer-%lu: (count ++)= %d(key = %d)\n", pthread_self(), count, *(int *)pthread_getspecific(key));
                sleep(rand() % 3);
        }
}

5 线程属性类函数pthread_attr_*

Posix线程中的线程属性pthread_attr_t主要包括scope属性、detach属性、堆栈地址、堆栈大小、优先级。
(1)初始化一个线程对象的属性
int pthread_attr_init(pthread_attr_t *attr);
返回值:若是成功返回0,否则返回错误的编号
形 参:
attr 指向一个线程属性的指针
说 明:Posix线程中的线程属性pthread_attr_t主要包括scope属性、detach属性、堆栈地址、堆栈大小、优先级。
pthread_attr_init实现时为属性对象分配了动态内存空间。
(2)销毁一个线程属性对象
int pthread_attr_destroy(pthread_attr_t *attr);
(3)获取线程的CPU亲缘性
int pthread_attr_getaffinity_np(pthread_attr_t *attr, size_t cpusetsize, cpu_set_t *cpuset);
说 明:获得线程属性的一个参数,该参数指明了使用该属性的线程会被绑定到哪个CPU上运行。
(4)设置线程的CPU亲缘性
int pthread_attr_setaffinity_np(pthread_attr_t *attr, size_t cpusetsize, const cpu_set_t *cpuset);
说 明:通过指定cupset来设置线程属性的参数—绑定到哪个CPU上运行。
(5)获取线程分离状态属性
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
(6)修改线程分离状态属性
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
形 参:
attr 指向一个线程属性的指针
detachstat 有两个取值
PTHREAD_CREATE_DETACHED(分离)
PTHREAD_CREATE_JOINABLE(非分离)
(7)获取线程的栈保护区大小
int pthread_attr_getguardsize(pthread_attr_t *attr, size_t *guardsize);
(8)设置线程的栈保护区大小
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
说 明:参数提供了对栈指针溢出的保护。
默认为系统页大小
如果设置为0表示没有保护区。
大于0,则会为每个使用 attr 创建的线程提供大小至少为 guardsize 字节的溢出保护区。
(9)获取线程的作用域
int pthread_attr_getscope(pthread_attr_t *attr, int *scope);
(10)设置线程的作用域
int pthread_attr_setscope(pthread_attr_t *attr, int scope);
形 参:
attr 指向一个线程属性的指针
guardsize 线程的作用域,可以取如下值
PTHREAD_SCOPE_SYSTEM 与系统中所有进程中线程竞争
PTHREAD_SCOPE_PROCESS 与当前进程中的其他线程竞争
说 明:指定了作用域也就指定了线程与谁竞争资源
(11)获取线程的堆栈信息(栈地址和栈大小)
int pthread_attr_getstack(pthread_attr_t *attr, void **stackaddr, size_t *stacksize);
(12)设置线程堆栈区
int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);
形 参:
attr 指向一个线程属性的指针
stackaddr 线程的堆栈地址:应该是可移植的,对齐页边距的
可以用posix_memalign来进行获取
stacksize 线程的堆栈大小:应该是页大小的整数倍
说 明:设置堆栈区,将导致pthread_attr_setguardsize失效。
(13)获取线程的调度策略
int pthread_attr_getschedpolicy(pthread_attr_t *attr, int *policy);
说 明:获取线程的调度策略
(14)设置线程的调度策略
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
形 参:
attr 指向一个线程属性的指针
policy 线程的调度策略,有如下三种:
SCHED_FIFO 先入先出策略
SCHED_RR 轮转调度,类似于 FIFO,但加上了时间轮片算法
SCHED_OTHER 系统默认策略
说 明:设置线程的调度策略
(15)获取线程的调度参数
int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);
形 参:
attr 指向一个线程属性的指针
param 返回获取的调度参数,该结构仅有一个从参数,如下
struct sched_param
{
int sched_priority; /* Scheduling priority */
};
说 明:获取线程的调度参数
(16)设置线程的调度参数
int pthread_attr_getschedparam(pthread_attr_t *attr, struct sched_param *param);
形 参:
attr 指向一个线程属性的指针
param 要设置的调度参数
说 明:设置线程的调度参数
(17)获取线程是否继承调度属性
int pthread_attr_getinheritsched(pthread_attr_t *attr, int *inheritsched);
说 明:获取线程是否继承调度属性
(18)设置线程是否继承调度属性
int pthread_attr_getinheritsched(pthread_attr_t *attr, int *inheritsched);
形 参:
attr 指向一个线程属性的指针
guardsize 设置线程是否继承调度属性
PTHREAD_INHERIT_SCHED:调度属性将继承于正创建的线程
attr中的设置将被忽略
PTHREAD_EXPLICIT_SCHED 调度属性将被设置为attr中指定的属性值

猜你喜欢

转载自blog.csdn.net/u011414616/article/details/81383717