python中的线程与linux中的线程

一:多任务的概念

   当计算机运行一个程序时,如果没有开启线程,那么整个程序就是单线程的,单线程的意思就是程序在main()函数中是顺序执行的,那么在小型程序中或许单线程执行没什么问题,但是如果遇到类似网络编程就会遇到阻塞的情况,这个时候,如果程序可以并行执行话,就可以方便很多。所以引入了线程机制。

二:python中的线程

   在python中想要使用线程,先引入threading模块,其实thread也可以,但是threading把thread又进行了一层封装。所以编程常用threading模块

import threading
import time

def saySorry():
    print("亲爱的,我错了,我能吃饭了吗?")
    time.sleep(1)

if __name__ == "__main__":
    for i in range(5):
        t = threading.Thread(target=saySorry)
        t.start() #启动线程,即让线程开始执行

  上面这段代码就是对于线程最简单的应用,可以看出,在for循环中创建了5个线程,threading模块中的thread类创建线程,参数有你想并行执行的函数名,还有参数,只是这里没有表示出来。这5个线程都执行一个函数saySorry。target就相当于callback,属于回调机制。这里要注意一点的就是,当线程对象调用start()方法时,线程才被真正的创建并执行。

线程的生命周期:一般来说,如果你创建了很多线程,但是呢,你的主线程并没有阻塞住并等待子线程结束的话,那么当主线程结束后,不论子线程执行到哪里,都会退出。所以一般调用线程里的别的方法,来使主线程等到子线程都结束的时候,在结束程序。

线程的执行顺序:操作系统对于线程和进程的调度是我们无法控制的,也就是说,没有顺序。所以如果我们非要让他有点顺序,那就要用别的方法去控制,例如sleep等等。

用面向对象的方法实现线程:

import threading
import time

class MyThread(threading.Thread):
    def run(self):
        for i in range(3):
            time.sleep(1)
            msg = "I'm "+self.name+' @ '+str(i) #name属性中保存的是当前线程的名字
            print(msg)


if __name__ == '__main__':
    t = MyThread()
    t.start()
  上述代码就是面向对象方法实现一个线程,不难理解,我们新建的这个线程类,继承了threading模块中的thread方法,在里面我们只要重写父类的run方法就可以实现线程。

 

线程参数传递:

from threading import Thread
import time

def work1(nums):
    nums.append(44)
    print("----in work1---",nums)


def work2(nums):
    #延时一会,保证t1线程中的事情做完
    time.sleep(1)
    print("----in work2---",nums)

g_nums = [11,22,33]

t1 = Thread(target=work1, args=(g_nums,))
t1.start()

t2 = Thread(target=work2, args=(g_nums,))
t2.start()

  从上述代码可以看出,args参数是一个元组,我们把我们想传递给线程的参数放到这个元组中,那么在线程函数里我们就可以使用了。

线程间共享资源:

from threading import Thread
import time

g_num = 100

def work1():
    global g_num
    for i in range(3):
        g_num += 1

    print("----in work1, g_num is %d---"%g_num)


def work2():
    global g_num
    print("----in work2, g_num is %d---"%g_num)


print("---线程创建之前g_num is %d---"%g_num)

t1 = Thread(target=work1)
t1.start()

#延时一会,保证t1线程中的事情做完
time.sleep(1)

t2 = Thread(target=work2)
t2.start()

上述代码的运行结果为

---线程创建之前g_num is 100---
----in work1, g_num is 103---
----in work2, g_num is 103---

可以看出,在一个程序内部,各线程共享全局变量,也可以在线程内部改变这个共享资源。这是一把双刃剑,即提供了便利,也提升了代码产生混乱的机率。

三 互斥锁

    那么在线程要用共享资源的这一情境下,互斥锁应运而生。其实互斥锁只是标志位的高级抽象。我们可以这么来思考这个互斥锁的概念,就好比我们都要去上厕所,甲先进去,但是如果门不锁的话,乙并不知道里面有人,乙也会进去,这样的情况是不允许发生的,所以,甲在上厕所的时候,就把门锁好,出来的时候,就把门解锁。那么以上的这个流程,就是互斥锁的应用,其实如果不用python或者linux内部的API我们依然可以编写代码来自己实现锁机制,怎么实现?就是定义一个全局变量,初始值为0,当甲在用的时候我们把这个变量加一,那么如果此时乙也要来竞争这个资源,那么当他发现,这个全局变量为1的时候,他就等待,直到甲出来,把全局变量再次变成0的时候,乙才能去使用这个资源。那么加一减一的操作又叫做原子操作。实际上,锁的机制在内部的原理也大致如此。那么下面放上一段代码:

import threading
import time

g_num = 0

def test1(num):
    global g_num
    for i in range(num):
        mutex.acquire()  # 上锁
        g_num += 1
        mutex.release()  # 解锁

    print("---test1---g_num=%d"%g_num)

def test2(num):
    global g_num
    for i in range(num):
        mutex.acquire()  # 上锁
        g_num += 1
        mutex.release()  # 解锁

    print("---test2---g_num=%d"%g_num)

# 创建一个互斥锁
# 默认是未上锁的状态
mutex = threading.Lock()

# 创建2个线程,让他们各自对g_num加1000000次
p1 = threading.Thread(target=test1, args=(1000000,))
p1.start()

p2 = threading.Thread(target=test2, args=(1000000,))
p2.start()

# 等待计算完成
while len(threading.enumerate()) != 1:
    time.sleep(1)

print("2个线程对同一个全局变量操作之后的最终结果是:%s" % g_num)

 这里再说一件事情,那就是,我们在上锁的时候,尽量减少锁内部的代码,这样,运行起来效率高,而且也便于维护。

四 死锁:

    那么什么是死锁呢,死锁就是我的等待你的资源先释放,你等待我的资源先释放。我们知道的是,计算机并没有退让意识,那么这种情况之下,就会死住。代码如下:

import threading
import time

class MyThread1(threading.Thread):
    def run(self):
        # 对mutexA上锁
        mutexA.acquire()

        # mutexA上锁后,延时1秒,等待另外那个线程 把mutexB上锁
        print(self.name+'----do1---up----')
        time.sleep(1)

        # 此时会堵塞,因为这个mutexB已经被另外的线程抢先上锁了
        mutexB.acquire()
        print(self.name+'----do1---down----')
        mutexB.release()

        # 对mutexA解锁
        mutexA.release()

class MyThread2(threading.Thread):
    def run(self):
        # 对mutexB上锁
        mutexB.acquire()

        # mutexB上锁后,延时1秒,等待另外那个线程 把mutexA上锁
        print(self.name+'----do2---up----')
        time.sleep(1)

        # 此时会堵塞,因为这个mutexA已经被另外的线程抢先上锁了
        mutexA.acquire()
        print(self.name+'----do2---down----')
        mutexA.release()

        # 对mutexB解锁
        mutexB.release()

mutexA = threading.Lock()
mutexB = threading.Lock()

if __name__ == '__main__':
    t1 = MyThread1()
    t2 = MyThread2()
    t1.start()
    t2.start()
   A锁里有等待B的资源释放,B锁里等A的资源先释放,那么这段代码运行之后,就会卡死。


三 linux下的线程使用

1.线程的生命周期 ****
  线程的创建,可以在主线程中或其它线程中任意位置来创建
  线程的结束,就是线程函数的结束
  
     线程的生命周期包括:就绪、运行、阻塞、终止
     就绪:线程能够运行,但是在等待可用的处理器。可能刚刚启动,或者刚刚从阻塞中恢复,或者被其它线程抢占。
     运行:线程正在运行。在单处理器系统中,只能有一个线程处于运行状态,在多处理器系统中,可能有多个线程处于运行态。
     阻塞:线程由于等待“处理器”外的其它条件而无法运行,如:条件变量的改变,加锁互质量或者等待I/O操作结束。

     终止:线程从线程函数中返回,或者调用pthread_exit,或者被取消,或随进程的终止而终止。线程终止后会完成所有的清理工作。


2.线程的唯一标识
    就是线程的编号
    线程ID用于唯一识别哪一个线程。线程ID用pthread_t类型来表示,在线程中,可以用如下函数获取线程ID:
    //获取线程ID
    Pthread_t pthread_self(void);
    在线程内部读取,其它线程无法读取
1)线程的创建    
    #include <pthread.h>
    //创建线程
    int pthread_create(pthread_t *thread,const pthread_attr_t *attr,  void *(*start_routine)(void*),void * arg);
    参数:thread 线程id的地址,在创建线程时,产生ID
          attr   属性 优先级
          start_routine 用来指定线程函数 void *(*)(void*)类型代表了函数必须有void* 返回,必须有void*形参
          arg    是线程函数的参数
   
2)线程的终止
   (1)线程函数自然结束  自然死亡
   (2)在线程函数中使用return 自杀
        线程函数内调用下面函数
        void pthread_exit(void *value_ptr);   
   (3)在其它线程使用下面函数 他杀
        int pthread_cancel(pthread_t thread);
   (4)随进程终止而终止  陪葬
3)等待线程结束  收尸
    //等待线程结束,如果这个线程没有结束,则函数处于阻塞模式
    int pthread_join(pthread_t thread, void **value_ptr);  
    参数: thread 已创建的线程ID号
           value_ptr: 是线程返回值的地址     
         
 用法:
 需要代码并行执行,则创建线程
 线程函数:循环(超过0.2秒)  如音频播
           耗时代码 (超过0.2秒)

1)线程的简单用法.
    //mplay.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    static int (*sendstate)(int,int);
    static int state;  //0 停止  1播放  2暂停
    static pthread_t id;
    void* run(void* arg){
        int i=0;
        int size=180;
        while(state){
           while(state==2) usleep(20000);//微秒
           //1) 读取磁盘文件
           //2) 解码
           //3) 写入声卡
           //4) 调用回调,将数据传入到UI层
           sendstate(state,i++);
           if (i>=size) break;
           sleep(1);
        }
        state=0;
        sendstate(state,i++); 
        return (void*)120;   
    }
    int play(char *file,int (*handler)(int,int)){
        if (state==0){
           //1.保存函数地址到全局
           sendstate=handler;
           state=1;
           //2.启动一个循环           
           pthread_create(&id,NULL,run,NULL); //创建线程
        }
        else state=1;
    }    
    int pause(){
        state=2;
        sendstate(state,0);
    }    
    int stop(){
        state=0;
        sendstate(state,0);
        int *a;
        pthread_join(id,&a);//等待结束
        printf("---- %d\n",a);//值为120
    }
2)线程函数的参数.
    格式
    void * 函数名(void *参数){
    }
    函数参数,是在创建线程由arg决定

    传整型
    void *run(void* arg){
        int a=*((int*)arg);
        while(1){
           printf("------- pthread %d\n",a);
           sleep(1);
        }
    }
    
    int main(int argc,char **argv){
        //pthread_t id[5];
        int i;
        for(i=0;i<5;i++){
           pthread_t id;
           pthread_create(&id,NULL,run,&i); //非阻塞
           usleep(200);
       }
        int b;
        scanf("%d",&b);
    }
    
    传数组
    void *run(void* arg){
        int p[7];
        memcpy(p,arg,4*7);
        int i=0;
        while(1){
           printf("------- pthread %d\n",p[i++%7]);
           sleep(1);
        }
        //free(p);
    }
    int main(int argc,char **argv){
    
        int a[]={1,2,3,4,5,6,7};
        int i;
        //for(i=0;i<5;i++){
           pthread_t id;
           pthread_create(&id,NULL,run,a); //非阻塞
           usleep(200);
       //}
        int b;
        scanf("%d",&b);
    }
    原则,尽快的将arg指向的内存数据拷到线程函数,以免在主函数中将arg指向的值改变。

3)线程属性的设置
   线程创建时,默认优先级为0  最大为50

   pthread_attr_t属性的类型
   typedef struct
    {
        int detachstate; //线程的分离状态
        int schedpolicy; //线程调度策略
        struct sched_param  schedparam;   //线程的调度参数,优先级
        int inheritsched; //线程的继承性
        int scope;        //线程的作用域
        size_t guardsize; //线程栈末尾的警戒缓冲区大小
        int stackaddr_set; 
        void * stackaddr; //设置线程栈的地址
        size_t stacksize; //设置线程栈的大小
    }pthread_attr_t;

   //初始化线程属性
  int pthread_attr_init ( pthread_attr_t *attr );
   //销毁线程属性
   int pthread_attr_destroy ( pthread_attr_t *attr );
    (2) 线程优先级
    //设置优先级结构体 
    int pthread_attr_setschedparam(pthread_attr_t *attr,const struct sched_param *param);
    int pthread_attr_getschedparam(const pthread_attr_t *attr,struct sched_param *param);
    struct sched_param结构如下:
    struct sched_param{   
       int __sched_priority; //所要设定的线程优先级
    };
int main(int argc,char **argv){
    int i;
    pthread_t id[5];
    pthread_attr_t attr;
    pthread_attr_init (&attr);
    struct sched_param par={50};
    pthread_attr_setschedparam(&attr,&par);
    for(i=0;i<5;i++){
       if (i==2) pthread_create(&id[i],NULL,run,&i); //创建线程,默认优先级
       else  pthread_create(&id[i],&attr,run,&i); //创建线程,同时设优先级
       usleep(200000);
    }
    pthread_attr_destroy(&attr);
    int b;
    scanf("%d",&b);
}







 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值