zoukankan      html  css  js  c++  java
  • linux多线程编程基础1

    今天突然想看一下linux下的多线程编程,对于多线程编程也有自己的理解,比如多个线程共享所在进程的所有资源,当然对这些资源的访问,有些时候需要我们互斥访问,所以得有线程锁,有时候可能我们需要线程间同步,那么就需要我们使用条件变量和信号量等等,多个线程每个线程都有一个线程栈,用来保存每个线程独有的状态转换,当然这些线程栈都是在进程的地址空间内,只不过他们的地址相互独立而已,当然如果我们能知道其他线程的地址,我们也可以非法去访问其他线程的内部变量。

    首先我想先介绍关于pthread线程库的一些东西:

    头文件:

    #include <pthread.h>

    函数原形:

    int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

    ///假如参数pthread_attr_t为NULL,那么它将采用默认值,而start_routine将接受一个函数指针,arg做为传进来函数的函数参数,当然也可以为空。

    ///返回值,如果返回为0,说明调用成功,其他值说明失败

    线程有两种类型:joinable or detached,那么有什么区别呢?

    joinable:如果一个线程是joinable,那么其他线程可以调用pthread_join(pthread_t thread, void **retval),来等待一个线程终止,并获得线程终止的状态码。然后释放它所占用的资源。当然也可以通过pthread_exit(void *retval)

    detached:如果一个线程是detached,那么一旦线程终止,即释放他所占用的资源。

    默认情况下,创建的线程是joinable的

    关于线程栈的大小?

    On Linux/x86-32, the default stack size for a new thread is  2 Mb,当然你也可以设置线程栈的大小,通过函数pthread_attr_setstacksize(3),the stack size

    attribute can be explicitly set in the attr argument used to create a thread, in order to obtain a stack size other than the default

    如何终止一个线程?

    方法1:

    void pthread_exit(void *retval); 

    在线程内部调用,同时将返回值保存在retval中。

    方法2:

    int pthread_cancel(pthread_t thread);

    It is canceled (see pthread_cancel(3)).

    方法3:

    进程中调用了exit(),则进程中所有的线程终止,或者主线程中执行了return from main()。

    Any of the threads in the process calls exit(3), or the main thread performs a return
    from main(). This causes the termination of all threads in the process.

    一个很重要的函数:

    int pthread_join(pthread_t thread, void **retval);

    这个函数主要作用是等待指定线程的终止,指定线程的id在thread中,并将返回值存放在retval中,如果指定线程由pthread_exit(void *retval)终止,则pthread_exit(void *retval)将他的返回值retval传递给pthread_join(pthread_t thread, void **retval)的retval。

    僵尸线程的问题:

    Failure to join with a thread that is joinable (i.e., one that is not detached), produces a "zombie thread". Avoid doing this, since each zombie thread consumes some system resources, and when enough zombie threads have accumulated, it will no longer be possible to create new threads (or processes).

     下面我写了一个关于创建线程的程序:

    #include <stdio.h>
    #include <pthread.h>
    #include <unistd.h>
    
    void* func(void* arg)
    {
        while(5)
        {
            printf("hello world\n");
        }
    }
    
    int main(void)
    {
        pthread_t thread;
        int n = 5;
        void* f = &func;
        int m = pthread_create(&thread, NULL, f, &n);
        return 0;
    }

    程序运行如下:

    完全没有任何结果,为什么呢?

    难道是是因为线程还没有开始运行,主线程(其实就是当前进程)的main函数已经运行结束?其实就是这个原因,因为线程间的调度是操作系统的控制的,按照时间片来分配的,不管是单个cpu还是多个cpu,只不过多个cpu的话,同时有多个线程运行而已。

    当然我们可以在主线程中写一个while循环来防止主线程运行结束。或者使用条件变量使进程挂起。

    难道就不能让主线程等待子线程运行完才开始return from main()吗?当然可以,pthread_join使一个线程等待另一个线程结束。代码中如果没有pthread_join,主线程会很快结束从而使整个进程结束,这样就会导致创建的线程没有机会运行就结束了,加入了pthread_join后,主线程会一直等待直到等待的子线程结束自己才结束,使创建的线程有机会执行。当然所有的线程都有一个线程号,也就是thread ID,其类型为pthread_t。通过调用pthread_self()函数可以获得自身的线程号。

    代码如下:

    #include <stdio.h>
    #include <pthread.h>
    #include <unistd.h>
    
    void* func(void* arg)
    {
        int* n = (int*) arg;  //注意这里的强制转换的运用
        while(*n)
        {
            (*n)--;
            printf("hello world\n");
        }
    }
    
    int main(void)
    {
        pthread_t thread;
        pthread_cond_t cond;
        int n = 5;
        void* f = &func;
        int m = pthread_create(&thread, NULL, f, &n);
        void** retval;
        pthread_join(thread,retval); //主线程会等待
       printf("retval: %d\n",retval);
    return 0; }

    当你将你的函数参数设为void* arg时,怎么去传递参数?当然可以可以随意传递一个指针,然后再将指针强制转换。

    运行结果如下:

    条件变量含义如下:

    条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。

    下面是关于使用while循环的操作,同时我们可以看看操作系统如何调度线程?

    #include <stdio.h>
    #include <pthread.h>
    #include <unistd.h>
    
    void* func(void* arg)
    {
        while(5)
        {
            sleep(1);
            printf("hello world\n");
        }
    }
    
    int main(void)
    {
        pthread_t thread;
        int n = 5;
        void* f = &func;
        int m = pthread_create(&thread, NULL, f, &n);
        while(1)
        {
            sleep(1);
            printf("nice to meet you\n");
        }
        return 0;
    }

    运行结果如下:

    那么又如何使用条件变量来控制操作系统对线程的调度呢?

    当然条件变量和互斥锁是不同的,条件变量是维护线程间同步,而互斥锁维护线程间互斥,那么同步的含义是什么呢?比如说一个寄存器,只有线程去写了寄存器,另外一个线程才能去读寄存器,而互斥锁是你访问和我访问不能同时进行,但是并不是说两个线程必须按照一定的循序去访问。

    #include <pthread.h>
    int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);
    

    int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);

    条件变量一般和互斥锁一起使用,为什么呢?因为所有等待这个条件变量的线程等待,所以可以维护一个等待条件成立的所有线程的队列,既然这边有一个等待条件成立的队列,对这个队列的访问必然是互斥的,也就是说对pthread_cond_wait()和 pthread_cond_timedwait()的函数调用必须是互斥的,当一个线程因等待条件成立,当然另一线程必然可以去访问条件变量,那么就必须让调用pthread_cond_wait()而挂起的线程去释放互斥锁,那么其他线程才可以请求的到锁,并且访问的到pthread_cond_wait()。

    整个编程模式如下:

    thread1:

    请求锁;///请求成功进入临界区,看条件变量是否成立

    if 条件是否成立:

      不成立则调用pthread_cond_wait(); //此时线程由运行态转换为阻塞态,同时释放锁和cpu,直到其他线程调用pthread_cond_signal(),发送信号,唤醒当前线程

    else:

      条件成立,真正进入临界区运行

      临界区操作;

      释放锁;///退出临界区

    thread2:

    也可能是如上操作

    thread3:

    请求锁;///请求成功进入临界区

    临界区操作;

    调用pthread_cond_signal(pthread_cond_t *cond); ///一旦调用了这个函数相当于给其他等待这个信号而挂起的线程,重新活起来的希望。

    释放锁;///退出临界区

     代码如下:

    #include <stdio.h>
    #include <pthread.h>
    #include <unistd.h>
    
    void* func(void* arg)
    {
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond,&mutex);
    
        int* n = (int*) arg;
        while(*n)
        {
            (*n)--;
            printf("hello world\n");
        }
        pthread_mutex_unlock(&mutex);
    }
    
    int main(void)
    {
        pthread_t thread;
        pthread_cond_t cond = PTHREAD_COND_INITIALIZER;  //我本来认为我把cond和mutex声明在这里,其实就是所有的线程共享了,大错特错,这些变量属于主线程的局部变量而已
        pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    
        int n = 5;
        void* f = &func;
        int m = pthread_create(&thread, NULL, f, &n);
        void** retval;
    
        pthread_mutex_lock(&mutex);
        printf("send signal to waited thread\n");
        pthread_cond_signal(cond);
        pthread_mutex_unlock(&mutex);
    
        pthread_join(thread,retval);
        printf("retval: %d\n",retval);
        return 0;
    }

     代码2如下:

    #include <unistd.h>
    #include <stdio.h>
    #include <pthread.h> //pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond; pthread_mutex_t mutex; int T = 0; void* func(void) { while(1) { pthread_mutex_lock(&mutex); if(T == 0) //进行条件判断,是否调用pthread_cond_wait(&cond,&mutex)挂起线程 { printf("the thread is running first?\n"); pthread_cond_wait(&cond,&mutex); //调用pthread_cond_wait(&cond,&mutex)挂起线程,当然对cond的访问也是互斥的 } int n = 5; while(n) { n--; printf("hello world\n"); } pthread_mutex_unlock(&mutex); sleep(1); } } int main(void) { pthread_mutex_init(&mutex, NULL); //初始化线程锁 pthread_cond_init(&cond, NULL); //初始化条件变量 pthread_t thread; void* f = &func; int m = pthread_create(&thread, NULL, f, NULL); //创建了线程一般还是主线程继续运行,然后主线程时间片用完,才开始切换到其他线程,
                                  //当然可以在主线程中调用sleep让主线程由运行态转换为阻塞态。 sleep(
    1); //马上调度到子线程 void** retval; while(1) { pthread_mutex_lock(&mutex); //一旦线程无法获得锁,线程将阻塞,释放cpu printf("send signal to waited thread\n"); T = 1; if(T == 1) pthread_cond_signal(&cond); //发送信号给阻塞线程,使他们重新分配时间片,运行 pthread_mutex_unlock(&mutex); sleep(1); } pthread_join(thread,retval); //pthread_join(thread,retval)这个函数的调用也会使调用进程阻塞,直到它等待的线程运行完成。 printf("retval: %d\n",retval); return 0; }

     运行结果如下:

    关于线程的状态转换:

    我觉得要真正了解线程的调度策略,和多线程编程的实质,最关键还是了解线程状态的转换。下面有一个关于状态转换,但是和pthread线程库还有一点差别。

    http://zy19982004.iteye.com/blog/1626916

    还没有写完,等待后续~~

  • 相关阅读:
    三种方法处理文字中的空格
    text——文本属性大全
    font——文字属性大全
    padding和margin——内边距和外边距
    background——背景属性
    C# 解析excel时,字段内有内容,却读取不到的解决方法
    jqprint 打印分页
    pre标签 首行会自动换行解决方案
    正则表达式 清除所有标签的属性
    针对安卓微信浏览器网页 置顶悬浮框浮动固定 的问题
  • 原文地址:https://www.cnblogs.com/GODYCA/p/2873625.html
Copyright © 2011-2022 走看看