标签归档:线程

Linux多线程-互斥&条件变量与同步

多线程代码问题描述

我们都知道,进程是操作系统对运行程序资源分配的基本单位,而线程是程序逻辑,调用的基本单位。在多线程的程序中,多个线程共享临界区资源,那么就会有问题:

比如

#include 
#include 
#include 
#include 

int g_val = 10;
void * test1(void* args)
{
    g_val = 20;
    printf("in %s: g_val = %dn",__func__, g_val);
}
void * test2(void* args)
{
    sleep(1);
    printf("in %s: g_val = %dn",__func__,g_val);
}
int main(int argc, char const *argv[])
{
    pthread_t id1,id2;
    pthread_create(&id1,NULL,test1,NULL);
    pthread_create(&id2,NULL,test2,NULL);
    pthread_join(id1,NULL);
    pthread_join(id2,NULL);
    return 0;
} 

由次我们可以看到,线程1修改了全局变量,而线程2中页跟着改变了。

那么,对于这个问题进行放大,我们就会找到多线程存在的问题。如下

#include 
#include 
// pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int g_val = 0;
void* add(void *argv)
{
    for(int i = 0 ; i < 5000; ++i)
    {
        // g_val++;
        // pthread_mutex_lock(&lock);
        int tmp = g_val;
        g_val = tmp+1;
        // pthread_mutex_unlock(&lock);
    }
}

int main(int argc, char const *argv[])
{
    pthread_t id1,id2;

    pthread_create(&id1,NULL,add,NULL);
    pthread_create(&id2,NULL,add,NULL);

    pthread_join(id1,NULL);
    pthread_join(id2,NULL);

    printf("%dn",g_val);
    return 0;
}

在上面代码中,我们执行两个线程分别对全局变量累加5000次,但是得到的结果却是不确定的。这是因为,在多线程程序中,线程调度使得线程间进行切换执行,如果当线程1将数据从内存读入cpu正在准备累加时,调度器切换线程2执行,此时,线程2获取的值是未累加的。那么,当两个线程都执行完本次累加后,实际值只增加了1。所以就会产生多次执行,结果不确定性。

注:代码中没有直接g_val++,而选择了tmp过度就是为了产生非原子操作,让调度过程处于累加未完时。

那么解决这个问题,就需要互斥操作了。

我们首先来谈互斥量mutex

通过互斥量实现线程锁,在每个线程累加之前,进行临界资源的锁操作,在结束时解锁,那么就能保证目标的实现了。

#include 
#include 
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int g_val = 0;
void* add(void *argv)
{
    for(int i = 0 ; i < 5000; ++i)
    {
        // g_val++;
        pthread_mutex_lock(&lock);
        int tmp = g_val;
        g_val = tmp+1;
        pthread_mutex_unlock(&lock);
    }
}

int main(int argc, char const *argv[])
{
    pthread_t id1,id2;

    pthread_create(&id1,NULL,add,NULL);
    pthread_create(&id2,NULL,add,NULL);

    pthread_join(id1,NULL);
    pthread_join(id2,NULL);

    printf("%dn",g_val);
    return 0;
}

关于互斥锁的实现,在linux中实现如下

条件变量

问题场景描述

假设我们现在需要做一个生产者消费者模型,生产者对带有头节点的链表头插方式push_front生产数据,消费者调用pop_front消费数据.而生产者可能动作比较慢,这时就会有问题。

生产者生产一个数据时间,消费者可能迫切需求。因此,一直轮寻申请锁资源,以便进行消费。所以就会产生多次不必的锁资源申请释放动作。影响系统性能。

#include 
#include 
#include 
#include 
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
typedef struct node 
{
    int _data;
    struct node *_next;
}node_t,* node_p,**node_pp;

node_p head = NULL;

node_p alloc_node(int data)
{
    node_p ret = (node_p)malloc(sizeof(node_t));
    ret->_data = data;
    ret->_next = NULL;
    return ret;
}

void init(node_pp phead)
{
    *phead = alloc_node(0);
}

void push_front(node_p head,int data)
{
    node_p tmp = alloc_node(data);
    tmp->_next = head->_next;
    head->_next = tmp;
}

void pop_front(node_p head, int * pdata)
{
    if(head->_next!=NULL)
    {
        node_p tmp = head->_next;
        head->_next = tmp->_next;

        *pdata = tmp->_data;
        free(tmp);        
    }
}

void show(node_p head)
{
    node_p cur = head->_next;
    while(cur)
    {
        printf("%d->", cur->_data);
        cur = cur->_next;
    }
    printf("n");
}

//消费者
void * consumer(void *argv)
{
    int data;
    while(1)
    {
        pthread_mutex_lock(&lock);
        // while(head->_next==NULL)
        if(head->_next==NULL)
        {
            printf("producter is not readyn");
            // pthread_cond_wait(&cond,&lock);
            // break;
        }
        else{
        printf("producter is ready...n");
        pop_front(head,&data);
        printf("%s  data = %d n",__func__, data);
        }
        pthread_mutex_unlock(&lock);

        sleep(1);
    }
}

void * producter(void * argv)
{
    int data = rand()%1234;
    while(1)
    {
        sleep(4);
        pthread_mutex_lock(&lock);
        push_front(head,data);
        printf("%s data :: %dn",__func__, data);
        pthread_mutex_unlock(&lock);
        // pthread_cond_signal(&cond);
    }
}

int main(int argc, char const *argv[])
{
    init(&head);

    pthread_t id1,id2;
    
    pthread_create(&id1,NULL,consumer,NULL);
    pthread_create(&id2,NULL,producter,NULL);

    pthread_join(id1,NULL);
    pthread_join(id2,NULL);
}

由上,我们发现。生产者生叉一个数据之后,消费者总是会多次进行锁资源申请并尝试消费数据。那么,解决这一问题的方案就是:条件变量。

具体实现如下:

#include 
#include 
#include 
#include 
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
typedef struct node 
{
    int _data;
    struct node *_next;
}node_t,* node_p,**node_pp;

node_p head = NULL;

node_p alloc_node(int data)
{
    node_p ret = (node_p)malloc(sizeof(node_t));
    ret->_data = data;
    ret->_next = NULL;
    return ret;
}

void init(node_pp phead)
{
    *phead = alloc_node(0);
}

void push_front(node_p head,int data)
{
    node_p tmp = alloc_node(data);
    tmp->_next = head->_next;
    head->_next = tmp;
}

void pop_front(node_p head, int * pdata)
{
    if(head->_next!=NULL)
    {
        node_p tmp = head->_next;
        head->_next = tmp->_next;

        *pdata = tmp->_data;
        free(tmp);        
    }
}

void show(node_p head)
{
    node_p cur = head->_next;
    while(cur)
    {
        printf("%d->", cur->_data);
        cur = cur->_next;
    }
    printf("n");
}

//消费者
void * consumer(void *argv)
{
    int data;
    while(1)
    {
        pthread_mutex_lock(&lock);
        while(head->_next==NULL)
        // if(head->_next==NULL)
        {
            printf("producter is not readynn");
            pthread_cond_wait(&cond,&lock);
            break;
        }
        // else{
        // printf("producter is ready...n");
        pop_front(head,&data);
        printf("%s  data = %d n",__func__, data);
        // }
        pthread_mutex_unlock(&lock);

        sleep(1);
    }
}

void * producter(void * argv)
{
    int data = rand()%1234;
    while(1)
    {
        sleep(4);
        pthread_mutex_lock(&lock);
        push_front(head,data);
        printf("%s data :: %dn",__func__, data);
        pthread_mutex_unlock(&lock);
        pthread_cond_signal(&cond); //条件变量v操作
    }
}

int main(int argc, char const *argv[])
{
    init(&head);

    pthread_t id1,id2;
    
    pthread_create(&id1,NULL,consumer,NULL);
    pthread_create(&id2,NULL,producter,NULL);

    pthread_join(id1,NULL);
    pthread_join(id2,NULL);
}

由图可以看出,这下我们的消费者不再进行过多次没必要的轮寻访问,当生产者生产数据时,告诉消费者可以进行消费了,那么消费者进行消费。

其实这也就是著名的:好莱坞原则—不要打电话给我们,我们会通知你。

eg,在面试笔试中,我们不需要过度的紧张是否被录用,只需要在做到最大努力之后等着招聘方通知就好。

注:一个Condition Variable总是和一个Mutex搭配使用的。一个线程可以调用
pthread_cond_wait在一一个Condition Variable上阻塞等待,这个函数做以下三步操作:
1. 释放Mutex
2. 阻塞等待
3. 当被唤醒时,重新获得Mutex并返回

Java线程中生产者与消费者的问题

一、概念

  生产者与消费者问题是一个金典的多线程协作的问题.生产者负责生产产品,并将产品存放到仓库;消费者从仓库中获取产品并消费。当仓库满时,生产者必须停止生产,直到仓库有位置存放产品;当仓库空时,消费者必须停止消费,直到仓库中有产品。

  解决生产者/消费者问题主要用到如下几个技术:1.用线程模拟生产者,在run方法中不断地往仓库中存放产品。2.用线程模拟消费者,在run方法中不断地从仓库中获取产品。3.仓库类保存产品,当产品数量为0时,调用wait方法,使得当前消费者线程进入等待状态,当有新产品存入时,调用notify方法,唤醒等待的消费者线程。当仓库满时,调用wait方法,使得当前生产者线程进入等待状态,当有消费者获取产品时,调用notify方法,唤醒等待的生产者线程。

二、实例

package book.thread.product;

public class Consumer extends Thread{
  private Warehouse warehouse;//消费者获取产品的仓库
   private boolean running = false;//是否需要结束线程的标志位
   public Consumer(Warehouse warehouse,String name){
    super(name);
    this.warehouse = warehouse;
  }
  public void start(){
    this.running = true;
    super.start();
  }
  public void run(){
    Product product;
    try {
      while(running){
        //从仓库中获取产品
         product = warehouse.getProduct();
        sleep(500);
      }
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
  //停止消费者线程
   public void stopConsumer(){
    synchronized(warehouse){
      this.running = false;
      warehouse.notifyAll();//通知等待仓库的线程
     }
  }
  //消费者线程是否在运行
   public boolean isRunning(){
    return running;
  }
}

 

package book.thread.product;

public class Producer extends Thread{
   private Warehouse warehouse;//生产者存储产品的仓库
   private static int produceName = 0;//产品的名字
   private boolean running = false;//是否需要结束线程的标志位

   public Producer(Warehouse warehouse,String name){
    super(name);
    this.warehouse = warehouse;
  }
  public void start(){
    this.running = true;
    super.start();
  }
  public void run(){
    Product product;
    //生产并存储产品
     try {
    while(running){
      product = new Product((++produceName)+””);
      this.warehouse.storageProduct(product);
      sleep(300);
      }
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
  //停止生产者线程
   public void stopProducer(){
    synchronized(warehouse){
      this.running = false;
      //通知等待仓库的线程
       warehouse.notifyAll();
    }
  }
  //生产者线程是否在运行
   public boolean isRunning(){
    return running;
  }
}

 

package book.thread.product;

public class Product {
  private String name;//产品名
   public Product(String name){
    this.name = name;
  }
  public String toString(){
    return “Product-“+name;
  }
}

 

package book.thread.product;

//产品的仓库类,内部采用数组来表示循环队列,以存放产品
public class Warehouse {
  private static int CAPACITY = 11;//仓库的容量
   private Product[] products;//仓库里的产品
   //[front,rear]区间的产品未被消费
   private int front = 0;//当前仓库中第一个未被消费的产品的下标
   private int rear = 0;//仓库中最后一个未被消费的产品下标加1
  public Warehouse(){
    this.products = new Product[CAPACITY];
  }
  public Warehouse(int capacity){
    this();
    if(capacity > 0){
      CAPACITY = capacity +1;
      this.products = new Product[CAPACITY];
    }
  }

  //从仓库获取一个产品
   public Product getProduct() throws InterruptedException{
    synchronized(this){
      boolean consumerRunning = true;//标志消费者线程是否还在运行
       Thread currentThread = Thread.currentThread();//获取当前线程
       if(currentThread instanceof Consumer){
        consumerRunning = ((Consumer)currentThread).isRunning();
      }else{
        return null;//非消费者不能获取产品
       }
      //若消费者线程在运行中,但仓库中没有产品了,则消费者线程继续等待
       while((front==rear) && consumerRunning){
        wait();
        consumerRunning = ((Consumer)currentThread).isRunning();
      }
      //如果消费者线程已经停止运行,则退出该方法,取消获取产品
       if(!consumerRunning){
        return null;
      }
      //获取当前未被消费的第一个产品
       Product product = products[front];
      System.out.println(“Consumer[” + currentThread.getName()+”] getProduct:”+product);
      //将当前未被消费产品的下标后移一位,如果到了数组末尾,则移动到首部
       front = (front+1+CAPACITY)%CAPACITY;
      System.out.println(“仓库中还没有被消费的产品数量:”+(rear+CAPACITY-front)%CAPACITY);
      //通知其他等待线程
       notify();
      return product;
    }
  }
  //向仓库存储一个产品
   public void storageProduct(Product product) throws InterruptedException{
  synchronized(this){
    boolean producerRunning = true;//标志生产者线程是否在运行
     Thread currentThread = Thread.currentThread();
    if(currentThread instanceof Producer){
      producerRunning = ((Producer)currentThread).isRunning();
    }else{
      return;
    }
    //如果最后一个未被消费的产品与第一个未被消费的产品的下标紧挨着,则说明没有存储空间了。
     //如果没有存储空间了,而生产者线程还在运行,则生产者线程等待仓库释放产品
     while(((rear+1)%CAPACITY == front) && producerRunning){
      wait();
      producerRunning = ((Producer)currentThread).isRunning();
    }
    //如果生产线程已经停止运行了,则停止产品的存储
     if(!producerRunning){
      return;
    }
    //保存产品到仓库
     products[rear] = product;
    System.out.println(“Producer[” + Thread.currentThread().getName()+”] storageProduct:” + product);
    //将rear下标循环后移一位
     rear = (rear + 1)%CAPACITY;
    System.out.println(“仓库中还没有被消费的产品数量:”+(rear + CAPACITY -front)%CAPACITY);
    notify();
    }
  }
}

 

package book.thread.product;

public class TestProduct {
  public static void main(String[] args) {
    Warehouse warehouse = new Warehouse(10);//建立一个仓库,容量为10
    //建立生产者线程和消费者
     Producer producers1 = new Producer(warehouse,”producer-1″);
    Producer producers2 = new Producer(warehouse,”producer-2″);
    Producer producers3 = new Producer(warehouse,”producer-3″);
    Consumer consumer1 = new Consumer(warehouse,”consumer-1″);
    Consumer consumer2 = new Consumer(warehouse,”consumer-2″);
    Consumer consumer3 = new Consumer(warehouse,”consumer-3″);
    Consumer consumer4 = new Consumer(warehouse,”consumer-4″);
    //启动生产者线程和消费者线程
     producers1.start();
    producers2.start();
    consumer1.start();
    producers3.start();
    consumer2.start();
    consumer3.start();
    consumer4.start();
    //让生产者/消费者程序运行1600ms
    try {
      Thread.sleep(1600);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    //停止消费者线程
     producers1.stopProducer();
    consumer1.stopConsumer();
    producers2.stopProducer();
    consumer2.stopConsumer();
    producers3.stopProducer();
    consumer3.stopConsumer();
    consumer4.stopConsumer();
  }
}

输出结果:

Producer[producer-1] storageProduct:Product-1
仓库中还没有被消费的产品数量:1
Consumer[consumer-2] getProduct:Product-1
仓库中还没有被消费的产品数量:0
Producer[producer-3] storageProduct:Product-3
仓库中还没有被消费的产品数量:1
Producer[producer-2] storageProduct:Product-2
仓库中还没有被消费的产品数量:2
Consumer[consumer-3] getProduct:Product-3
仓库中还没有被消费的产品数量:1
Consumer[consumer-1] getProduct:Product-2
仓库中还没有被消费的产品数量:0
Producer[producer-1] storageProduct:Product-4
仓库中还没有被消费的产品数量:1
Consumer[consumer-4] getProduct:Product-4
仓库中还没有被消费的产品数量:0
Producer[producer-3] storageProduct:Product-6
仓库中还没有被消费的产品数量:1
Producer[producer-2] storageProduct:Product-5
仓库中还没有被消费的产品数量:2
Consumer[consumer-1] getProduct:Product-6
仓库中还没��被消费的产品数量:1
Consumer[consumer-2] getProduct:Product-5
仓库中还没有被消费的产品数量:0
Producer[producer-1] storageProduct:Product-7
仓库中还没有被消费的产品数量:1
Consumer[consumer-3] getProduct:Product-7
仓库中还没有被消费的产品数量:0
Producer[producer-3] storageProduct:Product-8
仓库中还没有被消费的产品数量:1
Producer[producer-2] storageProduct:Product-9
仓库中还没有被消费的产品数量:2
Consumer[consumer-4] getProduct:Product-8
仓库中还没有被消费的产品数量:1
Producer[producer-1] storageProduct:Product-10
仓库中还没有被消费的产品数量:2
Producer[producer-3] storageProduct:Product-11
仓库中还没有被消费的产品数量:3
Producer[producer-2] storageProduct:Product-12
仓库中还没有被消费的产品数量:4
Consumer[consumer-1] getProduct:Product-9
仓库中还没有被消费的产品数量:3
Consumer[consumer-2] getProduct:Product-10
仓库中还没有被消费的产品数量:2
Consumer[consumer-3] getProduct:Product-11
仓库中还没有被消费的产品数量:1
Producer[producer-3] storageProduct:Product-13
仓库中还没有被消费的产品数量:2
Producer[producer-1] storageProduct:Product-14
仓库中还没有被消费的产品数量:3
Producer[producer-2] storageProduct:Product-15
仓库中还没有被消费的产品数量:4
Consumer[consumer-4] getProduct:Product-12
仓库中还没有被消费的产品数量:3
Consumer[consumer-1] getProduct:Product-13
仓库中还没有被消费的产品数量:2
Consumer[consumer-2] getProduct:Product-14
仓库中还没有被消费的产品数量:1
Producer[producer-1] storageProduct:Product-16
仓库中还没有被消费的产品数量:2
Producer[producer-3] storageProduct:Product-17
仓库中还没有被消费的产品数量:3
Producer[producer-2] storageProduct:Product-18
仓库中还没有被消费的产品数量:4

分析:在main方法中建立了一个产品仓库,并未该仓库关联了3个生产者线程和4个消费者线程,启动这些线程,使生产 者/消费者模型运作起来,当程序运行1600ms时,所有的生产者停止生产产品,消费者停止消费产品。

  生产者线程Product在run方法中没300ms便生产一个产品,并存入仓库;消费者线程Consumer在run方法中没500ms便从仓库中取一个产品。

  仓库类Warehouse负责存放产品和发放产品。storageProduct方法负责存储产品,当仓库满时,当前线程进入等待状态,即如果生产者线程A在调用storageProduct方法以存储产品时,发现仓库已满,无法存储时,便会进入等待状态。当存储产品成功时,调用notify方法,唤醒等待的消费者线程。

  getProduct方法负责提前产品,当仓库空时,当前线程进入等待状态,即如果消费者线程B在调用getProduct方法以获取产品时,发现仓库空了,便会进入等待状态。当提取产品成功时,调用notify方法,唤醒等待的生产者线程。

Qt学习笔记之可重入与线程安全

简述

本篇文章中,术语“可重入性”和“线程安全”被用来标记类与函数,以表明它们如何被应用在多线程应用程序中。

  • 一个线程安全的函数可以同时被多个线程调用,甚至调用者会使用共享数据也没有问题,因为对共享数据的访问是串行的。
  • 一个可重入函数也可以同时被多个线程调用,但是每个调用者只能使用自己的数据。

因此,一个线程安全的函数总是可重入的,但一个可重入的函数并不一定是线程安全的。

扩展开来,一个可重入的类,指的是它的成员函数可以被多个线程安全地调用,只要每个线程使用这个类的不同的对象。而一个线程安全的类,指的是它的成员函数能够被多线程安全地调用,即使所有的线程都使用该类的同一个实例也没有关系。

注意: Qt的一些类被设计为线程安全的,如果它们的目的是多线程。如果一个函数没有被标记为线程安全的或可重入的,它就不应该被不同的线程使用。如果一个类没有被标记为线程安全的或可重入的,该类的实例就不应该被多个线程访问。

  • 简述
  • 可重入性
  • 线程安全
  • Qt类的注意事项
  • 更多参考

 

可重入性

C++的类往往是可重入的,这只是因为它们只能访问自己的数据。任何线程都能访问一个可重入类实例的一个成员函数,只要同一时间没有其它线程调用该实例的成员函数。例如,下面的Counter类就是可重入的:

class Counter
{
public:
    Counter() { n = 0; }

    void increment() { ++n; }
    void decrement() { --n; }
    int value() const { return n; }

private:
    int n;
};

该类不是线程安全的,因为如果多个线程试图修改数据成员n,则结果是不确定的。这是因为++和–操作都不总是原子性的。事实上,它们一般被展开为3条机器指令:

  1. 将变量值装入寄存器
  2. 增加或减少寄存器中的值
  3. 将寄存器中的值写回内存

如果线程A和线程B同时将变量的旧值装入寄存器,增加寄存器中的值,再写回内存,它们最终会互相覆盖,导致变量值仅增加了一次!

线程安全

显然,访问应该是串行的: 线程A必须在无中断的情况下执行完1.2.3.三个步骤(原子性),然后线程B才能开始执行,反之亦然。一个使类是线程安全的简单方法就是用一个QMutex来保护数据成员的所有访问。

class Counter
{
public:
    Counter() { n = 0; }

    void increment() { QMutexLocker locker(&mutex); ++n; }
    void decrement() { QMutexLocker locker(&mutex); --n; }
    int value() const { QMutexLocker locker(&mutex); return n; }

private:
    mutable QMutex mutex;
    int n;
};

QMutexLocker类在其构造函数中自动锁定mutex,并且当析构函数被调用时解锁。锁定mutex保证了其它线程的访问都将是串行化的。mutex数据成员被声明为mutable的,这是因为value()是一个const函数,我们需要在其中lock和unlock该mutex。

Qt类的注意事项

许多Qt的类都是可重入的,但不是线程安全的,因为线程安全意味着为锁定与解锁一个QMutex增加额外的开销。例如:QString是可重入的,但不是线程安全的。你能够同时从多个线程访问不同的QString的实例,但不能同时从多个线程访问QString的同一个实例(除非用QMutex保护访问)。

有些Qt的类和函数是线程安全的。它们主要是线程相关类(例如:QMutex)和一些基本函数(例如: QCoreApplication::postEvent())。

注意: 多线程领域中的术语并不是完全标准化的。POSIX使用的可重入和线程安全的定义有些不用于它的C API。当Qt和其它面向对象的C++类库一起使用时,确保定义的理解。

android 主线程和子线程之间通过handler关联消息传递

从主线程发送消息到子线程(准确地说应该是非UI线程)

复制代码
 package com.zhuozhuo;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;

public class LooperThreadActivity extends Activity{
/** Called when the activity is first created. */

private final int MSG_HELLO = 0;
private Handler mHandler;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
new CustomThread().start();//新建并启动CustomThread实例

findViewById(R.id.send_btn).setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {//点击界面时发送消息
                String str = “hello”;
Log.d(“Test”, “MainThread is ready to send msg:” + str);
mHandler.obtainMessage(MSG_HELLO, str).sendToTarget();//发送消息到CustomThread实例

}
});

}

class CustomThread extends Thread {
@Override
public void run() {
//建立消息循环的步骤
            Looper.prepare();//1、初始化Looper
            mHandler = new Handler(){//2、绑定handler到CustomThread实例的Looper对象
                public void handleMessage (Message msg) {//3、定义处理消息的方法
                    switch(msg.what) {
case MSG_HELLO:
Log.d(“Test”, “CustomThread receive msg:” + (String) msg.obj);
}
}
};
Looper.loop();//4、启动消息循环
        }
}
}

复制代码

从非UI线程传递消息到UI线程(界面主线程),因为主界面已经有MessageQueue,所以可以直接获取消息处理消息。而上面由主线程向非UI线程中处理消息的时候,非UI线程需要先添加消息队列,然后处理消息循环。

复制代码

public class ThreadHandlerActivity extends Activity {
/** Called when the activity is first created. */

private static final int MSG_SUCCESS = 0;//获取图片成功的标识
    private static final int MSG_FAILURE = 1;//获取图片失败的标识

private ImageView mImageView;
private Button mButton;

private Thread mThread;

private Handler mHandler = new Handler() {
public void handleMessage (Message msg) {//此方法在ui线程运行
            switch(msg.what) {
case MSG_SUCCESS:
mImageView.setImageBitmap((Bitmap) msg.obj);//imageview显示从网络获取到的logo
                Toast.makeText(getApplication(), getApplication().getString(R.string.get_pic_success), Toast.LENGTH_LONG).show();
break;

case MSG_FAILURE:
Toast.makeText(getApplication(), getApplication().getString(R.string.get_pic_failure), Toast.LENGTH_LONG).show();
break;
}
}
};

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mImageView= (ImageView) findViewById(R.id.imageView);//显示图片的ImageView
        mButton = (Button) findViewById(R.id.button);
mButton.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
if(mThread == null) {
mThread = new Thread(runnable);
mThread.start();//线程启动
                }
else {
Toast.makeText(getApplication(), getApplication().getString(R.string.thread_started), Toast.LENGTH_LONG).show();
}
}
});
}

Runnable runnable = new Runnable() {

@Override
public void run() {//run()在新的线程中运行
            HttpClient hc = new DefaultHttpClient();
HttpGet hg = new HttpGet(“http://csdnimg.cn/www/images/csdnindex_logo.gif”);//获取csdn的logo
            final Bitmap bm;
try {
HttpResponse hr = hc.execute(hg);
bm = BitmapFactory.decodeStream(hr.getEntity().getContent());
catch (Exception e) {
mHandler.obtainMessage(MSG_FAILURE).sendToTarget();//获取图片失败
                return;
}
mHandler.obtainMessage(MSG_SUCCESS,bm).sendToTarget();//获取图片成功,向ui线程发送MSG_SUCCESS标识和bitmap对象

//            mImageView.setImageBitmap(bm); //出错!不能在非ui线程操作ui元素

//            mImageView.post(new Runnable() {//另外一种更简洁的发送消息给ui线程的方法。
//
//                @Override
//                public void run() {//run()方法会在ui线程执行
//                    mImageView.setImageBitmap(bm);
//                }
//            });
        }
};

复制代码

}

作者:Jackhuclan
出处:http://jackhuclan.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

java 多线程 之 CountDownLatch 代码示例

CountDownLatch,一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
这个概念和unix中的屏障(barrier)很相似,可能底层实现就是barrier。

屏障允许任意数量的线程等待,直到所有的线程完成处理工作,而线程不需要退出。所有线程达到屏障后可以接着工作。

#include <pthread.h>
int pthread_barrier_init(pthread_barrier_t *restrict barrier,const pthread_barrierattr_t *restirct attr, unsigned int count);
int pthread_barrier_destroy(pthread_barrier_t *barrier);

ccount指定屏障计数。
#include <pthread.h>
int pthread_barrier_wait(pthread_barrier_t *barrier);
调用pthread_barrier_wait的线程在屏障计数未满足条件时,进入休眠状态。如果满足屏障计数,所有的线程都被唤醒。
    其中一个线程会返回PTHREAD_BARRIER_SERIAL_THREAD,剩下的线程看到的返回值是0,这可以控制其中一个线程
    作为主线程,它可以工作在其他线程已经完成的工作结果上。

CountDownLatch的主要方法

 public CountDownLatch(int count);

 public void countDown();

 public void await() throws InterruptedException

构造方法参数指定了计数的次数

countDown方法,当前线程调用此方法,则计数减一

awaint方法,调用此方法会一直阻塞当前线程,直到计时器的值为0

例子

public class CountDownLatchDemo {  
    final static SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
    public static void main(String[] args) throws InterruptedException {  
        CountDownLatch latch=new CountDownLatch(2);//两个工人的协作  
        Worker worker1=new Worker("zhang san", 5000, latch);  
        Worker worker2=new Worker("li si", 8000, latch);  
        worker1.start();//  
        worker2.start();//  
        latch.await();//等待所有工人完成工作  
        System.out.println("all work done at "+sdf.format(new Date()));  
    }  
      
      
    static class Worker extends Thread{  
        String workerName;   
        int workTime;  
        CountDownLatch latch;  
        public Worker(String workerName ,int workTime ,CountDownLatch latch){  
             this.workerName=workerName;  
             this.workTime=workTime;  
             this.latch=latch;  
        }  
        public void run(){  
            System.out.println("Worker "+workerName+" do work begin at "+sdf.format(new Date()));  
            doWork();//工作了  
            System.out.println("Worker "+workerName+" do work complete at "+sdf.format(new Date()));  
            latch.countDown();//工人完成工作,计数器减一  
  
        }  
          
        private void doWork(){  
            try {  
                Thread.sleep(workTime);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
      
       
}  

输出:

Worker zhang san do work begin at 2011-04-14 11:05:11
Worker li si do work begin at 2011-04-14 11:05:11
Worker zhang san do work complete at 2011-04-14 11:05:16
Worker li si do work complete at 2011-04-14 11:05:19
all work done at 2011-04-14 11:05:19

Android – 线程同步

什么是线程同步?

    当使用多个线程来访问同一个数据时,非常容易出现线程安全问题(比如多个线程都在操作同一数据导致数据不一致),所以我们用同步机制来解决这些问题。

实现同步机制有两个方法:
1、同步代码块:
synchronized(同一个数据){} 同一个数据:就是N条线程同时访问一个数据。

 

2、同步方法:
public synchronized 数据返回类型方法名(){}

 

通过使用同步方法,可非常方便的将某类变成线程安全的类,具有如下特征:
1
,该类的对象可以被多个线程安全的访问。
2
,每个线程调用该对象的任意方法之后,都将得到正确的结果。
3
,每个线程调用该对象的任意方法之后,该对象状态依然保持合理状态。
注:synchronized关键字可以修饰方法,也可以修饰代码块,但不能修饰构造器,属性等

 

※不要对线程安全类的所有方法都进行同步,只对那些会改变共享资源方法的进行同步。

线程通讯:

    当使用synchronized 来修饰某个共享资源时(分同步代码块和同步方法两种情况),当某个线程获得共享资源的锁后就可以执行相应的代码段,直到该线程运行完该代码段后才释放对该共享资源的锁,让其他线程有机会执行对该共享资源的修改。当某个线程占有某个共享资源的锁时,如果另外一个线程也想获得这把锁运行就需要使用wait()
和notify()/notifyAll()方法来进行线程通讯了。
Java.lang.object 里的三个方法wait() notify()  notifyAll()


wait()

导致当前线程等待,直到其他线程调用同步监视器的notify方法或notifyAll方法来唤醒该线程。

wait(mills)

都是等待指定时间后自动苏醒,调用wait方法的当前线程会释放该同步监视器的锁定,可以不用notify或notifyAll方法把它唤醒。

notify()
唤醒在同步监视器上等待的单个线程,如果所有线程都在同步监视器上等待,则会选择唤醒其中一个线程,选择是任意性的,只有当前线程放弃对该同步监视器的锁定后,也就是使用wait方法后,才可以执行被唤醒的线程。

notifyAll()

唤醒在同步监视器上等待的所有的线程。只用当前线程放弃对该同步监视器的锁定后,也就是使用wait方法后,才可以执行被唤醒的线程。

 

 

==================================================================================================

 

 

原子操作:根据Java规范,对于基本类型的赋值或者返回值操作,是原子操作。但这里的基本数据类型不包括longdouble, 因为JVM看到的基本存储单位是32位,而long double都要用64位来表示。所以无法在一个时钟周期内完成。 

自增操作(++)不是原子操作,因为它涉及到一次读和一次写。 

原子操作:由一组相关的操作完成,这些操作可能会操纵与其它的线程共享的资源,为了保证得到正确的运算结果,一个线程在执行原子操作其间,应该采取其他的措施使得其他的线程不能操纵共享资源。 

同步代码块:为了保证每个线程能够正常执行原子操作,Java引入了同步机制,具体的做法是在代表原子操作的程序代码前加上synchronized标记,这样的代码被称为同步代码块。 

同步锁:每个JAVA对象都有且只有一个同步锁,在任何时刻,最多只允许一个线程拥有这把锁。 

当一个线程试图访问带有synchronized(this)标记的代码块时,必须获得 this关键字引用的对象的锁,在以下的两种情况下,本线程有着不同的命运。 
1
 假如这个锁已经被其它的线程占用,JVM就会把这个线程放到本对象的锁池中。本线程进入阻塞状态。锁池中可能有很多的线程,等到其他的线程释放了锁,JVM就会从锁池中随机取出一个线程,使这个线程拥有锁,并且转到就绪状态。 
2
 假如这个锁没有被其他线程占用,本线程会获得这把锁,开始执行同步代码块。 (一般情况下在执行同步代码块时不会释放同步锁,但也有特殊情况会释放对象锁 如在执行同步代码块时,遇到异常而导致线程终止,锁会被释放;在执行代码块时,执行了锁所属对象的wait()方法,这个线程会释放对象锁,进入对象的等待池中 


线程同步的特征: 
1
 如果一个同步代码块和非同步代码块同时操作共享资源,仍然会造成对共享资源的竞争。因为当一个线程执行一个对象的同步代码块时,其他的线程仍然可以执行对象的非同步代码块。(所谓的线程之间保持同步,是指不同的线程在执行同一个对象的同步代码块时,因为要获得对象的同步锁而互相牵制) 
2
 每个对象都有唯一的同步锁 
3
 在静态方法前面可以使用synchronized修饰符。 
4
 当一个线程开始执行同步代码块时,并不意味着必须以不间断的方式运行,进入同步代码块的线程可以执行Thread.sleep()或执行Thread.yield()方法,此时它并不释放对象锁,只是把运行的机会让给其他的线程。 
5
 Synchronized声明不会被继承,如果一个用synchronized修饰的方法被子类覆盖,那么子类中这个方法不在保持同步,除非用synchronized修饰。 

释放对象的锁: 
1
 执行完同步代码块就会释放对象的锁 
2
 在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放 
3
 在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放对象锁,进入对象的等待池。 

死锁:

线程1独占(锁定)资源A,等待获得资源B后,才能继续执行
同时
线程2独占(锁定)资源B,等待获得资源A后,才能继续执行
这样就会发生死锁,程序无法正常执行

 

如何避免死锁 
一个通用的经验法则是:当几个线程都要访问共享资源ABC 时,保证每个线程都按照同样的顺序去访问他们。

 

 

==================================================================================================

注意:

1、线程同步就是线程排队。同步就是排队。线程同步的目的就是避免线程“同步”执行。

2、只有共享资源的读写访问才需要同步。如果不是共享资源,那么就根本没有同步的必要。
3、只有“变量”才需要同步访问。如果共享的资源是固定不变的,那么就相当于“常量”,线程同时读取常量也不需要同步。至少一个线程修改共享资源,这样的情况下,线程之间就需要同步。
4、多个线程访问共享资源的代码有可能是同一份代码,也有可能是不同的代码;无论是否执行同一份代码,只要这些线程的代码访问同一份可变的共享资源,这些线程之间就需要同步。

同步锁:

    我们可以给共享资源加一把锁,这把锁只有一把钥匙。哪个线程获取了这把钥匙,才有权利访问该共享资源。

同步锁不是加在共享资源上,而是加在访问共享资源的代码段上。

访问同一份共享资源的不同代码段,应该加上同一个同步锁;如果加的是不同的同步锁,那么根本就起不到同步的作用,没有任何意义。
这就是说,
同步锁本身也一定是多个线程之间的共享对象。

public static final Object lock1 = new Object();

… f1() {

synchronized(lock1){ // lock1 是公用同步锁
  // 代码段 A
// 访问共享资源 resource1
// 需要同步
}

你不一定要把同步锁声明为static或者public,但是你一定要保证相关的同步代码之间,一定要使用同一个同步锁。
任何一个Object Reference都可以作为同步锁。我们可以把Object Reference理解为对象在内存分配系统中的内存地址。因此,要保证同步代码段之间使用的是同一个同步锁,我们就要保证这些同步代码段的synchronized关键字使用的是同一个Object Reference,同一个内存地址。这也是为什么我在前面的代码中声明lock1的时候,使用了final关键字,这就是为了保证lock1的Object
Reference在整个系统运行过程中都保持不变。

竞争同步锁失败的线程进入的是该同步锁的就绪(Ready)队列,而不是后面要讲述的待召队列(Waiting Queue,也可以翻译为等待队列)。就绪队列里面的线程总是时刻准备着竞争同步锁,时刻准备着运行。而待召队列里面的线程则只能一直等待,直到等到某个信号的通知之后,才能够转移到就绪队列中,准备运行。

 

同步粒度
在Java语言里面,我们可以直接把synchronized关键字直接加在函数的定义上。
比如。
… synchronized … f1() {
 // f1 代码段
}

这段代码就等价于
… f1() {
 synchronized(this){ // 同步锁就是对象本身
   // f1 代码段
 }
}

同样的原则适用于静态(static)函数
比如。
… static synchronized … f1() {
 // f1 代码段
}

这段代码就等价于
…static … f1() {
 synchronized(Class.forName(…)){ // 同步锁是类定义本身
   // f1 代码段
  }
}

但是,我们要尽量避免这种直接把synchronized加在函数定义上的偷懒做法。因为我们要控制同步粒度。同步的代码段越小越好。synchronized控制的范围越小越好。

 


 

===============================附:JAVA开发实战经典 源码===============================

package com.synchronization;
class Info{ // 定义信息类
 private String name = “李兴华”;  // 定义name属性
 private String content = “JAVA讲师”  ;  // 定义content属性
 private boolean flag = false ; // 设置标志位
 
public synchronized void set(String name,String content){
 if(!flag){
  try{
   super.wait() ;
  }catch(InterruptedException e){
   e.printStackTrace() ;
  }
 }
 this.setName(name) ; // 设置名称
  try{
  Thread.sleep(300) ;
 }catch(InterruptedException e){
  e.printStackTrace() ;
 }
 this.setContent(content) ; // 设置内容
  flag  = false ; // 改变标志位,表示可以取走
  super.notify() ;
}

public synchronized void get(){
 if(flag){
  try{
   super.wait() ;
  }catch(InterruptedException e){
   e.printStackTrace() ;
  }
 }
 try{
  Thread.sleep(300) ;
 }catch(InterruptedException e){
  e.printStackTrace() ;
 }
 System.out.println(this.getName() +
  ” –> ” + this.getContent()) ;
 flag  = true ; // 改变标志位,表示可以生产
  super.notify() ;
}
public void setName(String name){
 this.name = name ;
}
public void setContent(String content){
 this.content = content ;
}
public String getName(){
 return this.name ;
}
public String getContent(){
 return this.content ;
}
}

class Producer implements Runnable{ // 通过Runnable实现多线程
 private Info info = null ;  // 保存Info引用
 public Producer(Info info){
 this.info = info ;
}
public void run(){
 boolean flag = false ; // 定义标记位
  for(int i=0;i<50;i++){
  if(flag){
   this.info.set(“李兴华”,”JAVA讲师”) ; // 设置名称
    flag = false ;
  }else{
   this.info.set(“mldn”,”
www.mldnjava.cn“)
// 设置名称
    flag = true ;
  }
 }
}
}

class Consumer implements Runnable{
private Info info = null ;
public Consumer(Info info){
 this.info = info ;
}
public void run(){
 for(int i=0;i<50;i++){
  this.info.get() ;
 }
}
}

public class ThreadCaseDemo{
public static void main(String args[]){
 Info info = new Info(); // 实例化Info对象
  Producer pro = new Producer(info) ; // 生产者
  Consumer con = new Consumer(info) ; // 消费者
  new Thread(pro).start() ;
 new Thread(con).start() ;
}
}

 

iOS多线程编程指南(二)线程管理

线程管理

Mac OS X和iOS里面的每个进程都是有一个或多个线程构成,每个线程都代表一个代码的执行路径。每个应用程序启动时候都是一个线程,它执行程序的main函数。应用程序可以生成额外的线程,其中每个线程执行一个特定功能的代码。

当应用程序生成一个新的线程的时候,该线程变成应用程序进程空间内的一个实体。每个线程都拥有它自己的执行堆栈,由内核调度独立的运行时间片。一个线程可以和其他线程或其他进程通信,执行I/O操作,甚至执行任何你想要它完成的任务。因为它们处于相同的进程空间,所以一个独立应用程序里面的所有线程共享相同的虚拟内存空间,并且具有和进程相同的访问权限。

本章提供了Mac OS X和iOS上面可用线程技术的预览,并给出了如何在你的应用程序里面使用它们的例子。

注意:获取关于Mac OS上面线程架构,或者更多关于线程的背景资料。请参阅技术说明TN2028 –“线程架构”。

1.1        线程成本

多线程会占用你应用程序(和系统的)的内存使用和性能方面的资源。每个线程都需要分配一定的内核内存和应用程序内存空间的内存。管理你的线程和协调其调度所需的核心数据结构存储在使用Wired Memory的内核里面。你线程的堆栈空间和每个线程的数据都被存储在你应用程序的内存空间里面。这些数据结构里面的大部分都是当你首次创建线程或者进程的时候被创建和初始化的,它们所需的代价成本很高,因为需要和内核交互。

表2-1量化了在你应用程序创建一个新的用户级线程所需的大致成本。这些成本里面的部分是可配置的,比如为辅助线程分配堆栈空间的大小。创建一个线程所需的时间成本是粗略估计的,仅用于当互相比较的时候。线程创建时间很大程度依赖于处理器的负载,计算速度,和可用的系统和程序空间。

Table 2-1  Thread creation costs

Item

Approximate cost

Notes

Kernel data structures

Approximately 1 KB

This memory is used to store the thread data structures and attributes, much of which is allocated as wired memory and therefore cannot be paged to disk.

Stack space

512 KB (secondary threads)

8 MB (Mac OS X main thread)

1 MB (iOS main thread)

The minimum allowed stack size for secondary threads is 16 KB and the stack size must be a multiple of 4 KB. The space for this memory is set aside in your process space at thread creation time, but the actual pages associated with that memory are not created until they are needed.

Creation time

Approximately 90 microseconds

This value reflects the time between the initial call to create the thread and the time at which the thread’s entry point routine began executing. The figures were determined by analyzing the mean and median values generated during thread creation on an Intel-based iMac with a 2 GHz Core Duo processor and 1 GB of RAM running Mac OS X v10.5.

注意:因为底层内核的支持,操作对象(Operation objectis)可能创建线程更快。它们使用内核里面常驻线程池里面的线程来节省创建的时间,而不是每次都创建新的线程。关于更多使用操作对象(Operation objects)的信息,参阅并发编程指南(Concurrency Programming Guide)。

当编写线程代码时另外一个需要考虑的成本是生产成本。设计一个线程应用程序有时会需要根本性改变你应用程序数据结构的组织方式。要做这些改变可能需要避免使用同步,因为本身设计不好的应用可能会造成巨大的性能损失。设计这些数据结构和在线程代码里面调试问题会增加开发一个线程应用所需的时间。然而避免这些消耗的话,可能在运行时候带来更大的问题,如果你的多线程花费太多的时间在锁的等待而没有做任何事情。

1.1        创建一个线程

创建低级别的线程相对简单。在所有情况下,你必须有一个函数或方法作为线程的主入口点,你必须使用一个可用的线程例程启动你的线程。以下几个部分介绍了比较常用线程创建的基本线程技术。线程创建使用了这些技术的继承属性的默认设置,由你所使用的技术来决定。关于更多如何配置你的线程的信息,参阅“线程属性配置”部分。

1.1.1    使用NSThread

使用NSThread来创建线程有两个可以的方法:

  1. 使用detachNewThreadSelector:toTarget:withObject:类方法来生成一个新的线程。
  2. 创建一个新的NSThread对象,并调用它的start方法。(仅在iOS和Mac OS X v10.5及其之后才支持)

这两种创建线程的技术都在你的应用程序里面新建了一个脱离的线程。一个脱离的线程意味着当线程退出的时候线程的资源由系统自动回收。这也同样意味着之后不需要在其他线程里面显式的连接(join)。因为detachNewThreadSelctor:toTarget:withObject:方法在Mac OS X的任何版本都支持,所以在Cocoa应用里面使用多线程的地方经常可以发现它。为了生成一个新的线程,你只要简单的提供你想要使用为线程主体入口的方法的名称(被指定为一个selector),和任何你想在启动时传递给线程的数据。下面的示例演示了这种方法的基本调用,来使用当前对象的自定义方法来生成一个线程。

1
[NSThread detachNewThreadSelector:@selector(myThreadMainMethod:) toTarget:self withObject:nil];

在Mac OS X v10.5之前,你使用NSThread类来生成多线程。虽然你可以获取一个NSThread对象并访问线程的属性,但你只能在线程运行之后在其内部做到这些。在Mac OS X v10.5支持创建一个NSThread对象,而无需立即生成一个相应的新线程(这些在iOS里面同样可用)。新版支持使得在线程启动之前获取并设置线程的很多属性成为可能。这也让用线程对象来引用正在运行的线程成为可能。

在Mac OS X v10.5及其之后初始化一个NSThread对象的简单方法是使用initWithTarget:selector:object:方法。该方法和detachNewThreadSelector:toTarget:withObject:方法来初始化一个新的NSThread实例需要相同的额外开销。然而它并没有启动一个线程。为了启动一个线程,你可以显式调用先对象的start方法,如下面代码:

1
2
3
4
NSThread* myThread = [[NSThread alloc] initWithTarget:self
                                        selector:@selector(myThreadMainMethod:)
                                        object:nil];
[myThread start];  // Actually create the thread

注意:使用initWithTarget:selector:object:方法的替代办法是子类化NSThread,并重写它的main方法。你可以使用你重写的该方法的版本来实现你线程的主体入口。更多信息,请参阅NSThread Class Reference里面子类化的提示。

如果你拥有一个NSThread对象,它的线程当前真正运行,你可以给该线程发送消息的唯一方法是在你应用程序里面的任何对象使用performSelector:onThread:withObject:waitUntilDone:方法。在Mac OS X v10.5支持在多线程上面执行selectors(而不是在主线程里面),并且它是实现线程间通信的便捷方法。你使用该技术时所发送的消息会被其他线程作为run-loop主体的一部分直接执行(当然这些意味着目标线程必须在它的run loop里面运行,参阅“ Run Loops”)。当你使用该方法来实现线程通信的时候,你可能仍然需要一个同步操作,但是这比在线程间设置通信端口简单多了。

注意:虽然在线程间的偶尔通信的时候使用该方法很好,但是你不能周期的或频繁的使用performSelector:onThread:withObject:waitUntilDone:来实现线程间的通信。

关于线程间通信的可选方法,参阅“设置线程的脱离状态”部分。

1.1.2    使用POSIX的多线程

Mac OS X和iOS提供基于C语言支持的使用POSIX线程API来创建线程的方法。该技术实际上可以被任何类型的应用程序使用(包括Cocoa和Cocoa Touch的应用程序),并且如果你当前真为多平台开发应用的话,该技术可能更加方便。你使用来创建线程的POSIX例程被调用的时候,使用pthread_create刚好足够。

列表2-1显示了两个使用POSIX来创建线程的自定义函数。LaunchThread函数创建了一个新的线程,该线程的例程由PosixThreadMainRoutine函数来实现。因为POSIX创建的线程默认情况是可连接的(joinable),下面的例子改变线程的属性来创建一个脱离的线程。把线程标记为脱离的,当它退出的时候让系统有机会立即回收该线程的资源。

Listing 2-1  Creating a thread in C

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include  <assert.h>
#include  <pthread.h>
void* PosixThreadMainRoutine(void* data)
{
    // Do some work here.
    return NULL;
}
void LaunchThread()
{
    // Create the thread using POSIX routines.
    pthread_attr_t  attr;
    pthread_t       posixThreadID;
    int             returnVal;
    returnVal = pthread_attr_init(&attr);
    assert(!returnVal);
    returnVal = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    assert(!returnVal);
    int     threadError = pthread_create(&posixThreadID, &attr, &PosixThreadMainRoutine, NULL);
    returnVal = pthread_attr_destroy(&attr);
    assert(!returnVal);
    if (threadError != 0)
    {
         // Report an error.
    }
}

如果你把上面列表的代码添加到你任何一个源文件,并且调用LaunchThread函数,它将会在你的应用程序里面创建一个新的脱离线程。当然,新创建的线程使用该代码没有做任何有用的事情。线程将会加载并立即退出。为了让它更有兴趣,你需要添加代码到PosixThreadMainRoutine函数里面来做一些实际的工作。为了保证线程知道该干什么,你可以在创建的时候给线程传递一个数据的指针。把该指针作为pthread_create的最后一个参数。

为了在新建的线程里面和你应用程序的主线程通信,你需要建立一条和目标线程之间的稳定的通信路径。对于基于C语言的应用程序,有几种办法来实现线程间的通信,包括使用端口(ports),条件(conditions)和共享内存(shared memory)。对于长期存在的线程,你应该几乎总是成立某种线程间的通信机制,让你的应用程序的主线程有办法来检查线程的状态或在应用程序退出时干净关闭它。

关于更多介绍POSIX线程函数的信息,参阅pthread的主页。

1.1.3    使用NSObject来生成一个线程

在iOS和Mac OS X v10.5及其之后,所有的对象都可能生成一个新的线程,并用它来执行它任意的方法。方法performSelectorInBackground:withObject:新生成一个脱离的线程,使用指定的方法作为新线程的主体入口点。比如,如果你有一些对象(使用变量myObj来代表),并且这些对象拥有一个你想在后台运行的doSomething的方法,你可以使用如下的代码来生成一个新的线程:

1
[myObj performSelectorInBackground:@selector(doSomething) withObject:nil];

调用该方法的效果和你在当前对象里面使用NSThread的detachNewThreadSelector:toTarget:withObject:传递selectore,object作为参数的方法一样。新的线程将会被立即生成并运行,它使用默认的设置。在selectore内部,你必须配置线程就像你在任何线程里面一样。比如,你可能需要设置一个自动释放池(如果你没有使用垃圾回收机制),在你要使用它的时候配置线程的run loop。关于更是介绍如果配置线程的信息,参阅“配置线程属性”部分。

1.1.4    使用其他线程技术

尽管POSIX例程和NSThread类被推荐使用来创建低级线程,但是其他基于C语言的技术在Mac OS X上面同样可用。在这其中,唯一一个可以考虑使用的是多处理服务(Multiprocessing Services),它本身就是在POSIX线程上执行。多处理服务是专门为早期的Mac OS版本开发的,后来在Mac OS X里面的Carbon应用程序上面同样适用。如果你有代码真是有该技术,你可以继续使用它,尽管你应该把这些代码转化为POSIX。该技术在iOS上面不可用。

关于更多如何使用多处理服务的信息,参阅多处理服务编程指南(Multiprocessing Services Programming Guide)

1.1.5    在Cocoa程序上面使用POSIX线程

经管NSThread类是Cocoa应用程序里面创建多线程的主要接口,如果可以更方便的话你可以任意使用POSIX线程带替代。例如,如果你的代码里面已经使用了它,而你又不想改写它的话,这时你可能需要使用POSIX多线程。如果你真打算在Cocoa程序里面使用POSIX线程,你应该了解如果在Cocoa和线程间交互,并遵循以下部分的一些指南。

u  Cocoa框架的保护

对于多线程的应用程序,Cocoa框架使用锁和其他同步方式来保证代码的正确执行。为了保护这些锁造成在单线程里面性能的损失,Cocoa直到应用程序使用NSThread类生成它的第一个新的线程的时候才创建这些锁。如果你仅且使用POSIX例程来生成新的线程,Cocoa不会收到关于你的应用程序当前变为多线程的通知。当这些刚好发生的时候,涉及Cocoa框架的操作哦可能会破坏甚至让你的应用程序崩溃。

为了让Cocoa知道你正打算使用多线程,你所需要做的是使用NSThread类生成一个线程,并让它立即退出。你线程的主体入口点不需要做任何事情。只需要使用NSThread来生成一个线程就足够保证Cocoa框架所需的锁到位。

如果你不确定Cocoa是否已经知道你的程序是多线程的,你可以使用NSThread的isMultiThreaded方法来检验一下。

u  混合POSIX和Cocoa的锁

在同一个应用程序里面混合使用POSIX和Cocoa的锁很安全。Cocoa锁和条件对象基本上只是封装了POSIX的互斥体和条件。然而给定一个锁,你必须总是使用同样的接口来创建和操纵该锁。换言之,你不能使用Cocoa的NSLock对象来操纵一个你使用pthread_mutex_init函数生成的互斥体,反之亦然。

1.2        配置线程属性

创建线程之后,或者有时候是之前,你可能需要配置不同的线程环境。以下部分描述了一些你可以做的改变,和在什么时候你需要做这些改变。

1.2.1    配置线程的堆栈大小

对于每个你新创建的线程,系统会在你的进程空间里面分配一定的内存作为该线程的堆栈。该堆栈管理堆栈帧,也是任何线程局部变量声明的地方。给线程分配的内存大小在“线程成本”里面已经列举了。

如果你想要改变一个给定线程的堆栈大小,你必须在创建该线程之前做一些操作。所有的线程技术提供了一些办法来设置线程堆栈的大小。虽然可以使用NSThread来设置堆栈大小,但是它只能在iOS和Mac OS X v10.5及其之后才可用。表2-2列出了每种技术的对于不同的操作。

Table 2-2  Setting the stack size of a thread

Technology Option
Cocoa In iOS and Mac OS X v10.5 and later, allocate and initialize an NSThread object (do not use thedetachNewThreadSelector:toTarget:withObject: method). Before calling the start method of the thread object, use thesetStackSize: method to specify the new stack size.
POSIX Create a new pthread_attr_t structure and use the pthread_attr_setstacksize function to change the default stack size. Pass the attributes to the pthread_create function when creating your thread.
Multiprocessing Services Pass the appropriate stack size value to the MPCreateTask function when you create your thread.

1.2.2    配置线程本地存储

每个线程都维护了一个键-值的字典,它可以在线程里面的任何地方被访问。你可以使用该字典来保存一些信息,这些信息在整个线程的执行过程中都保持不变。比如,你可以使用它来存储在你的整个线程过程中Run loop里面多次迭代的状态信息。

Cocoa和POSIX以不同的方式保存线程的字典,所以你不能混淆并同时调用者两种技术。然而只要你在你的线程代码里面坚持使用了其中一种技术,最终的结果应该是一样的。在Cocoa里面,你使用NSThread的threadDictionary方法来检索一个NSMutableDictionary对象,你可以在它里面添加任何线程需要的键。在POSIX里面,你使用pthread_setspecific和pthread_getspecific函数来设置和访问你线程的键和值。

1.2.3    设置线程的脱离状态

大部分上层的线程技术都默认创建了脱离线程(Datached thread)。大部分情况下,脱离线程(Detached thread)更受欢迎,因为它们允许系统在线程完成的时候立即释放它的数据结构。脱离线程同时不需要显示的和你的应用程序交互。意味着线程检索的结果由你来决定。相比之下,系统不回收可连接线程(Joinable thread)的资源直到另一个线程明确加入该线程,这个过程可能会阻止线程执行加入。

你可以认为可连接线程类似于子线程。虽然你作为独立线程运行,但是可连接线程在它资源可以被系统回收之前必须被其他线程连接。可连接线程同时提供了一个显示的方式来把数据从一个正在退出的线程传递到其他线程。在它退出之前,可连接线程可以传递一个数据指针或者其他返回值给pthread_exit函数。其他线程可以通过pthread_join函数来拿到这些数据。

重要:在应用程序退出时,脱离线程可以立即被中断,而可连接线程则不可以。每个可连接线程必须在进程被允许可以退出的时候被连接。所以当线程处于周期性工作而不允许被中断的时候,比如保存数据到硬盘,可连接线程是最佳选择。

如果你想要创建可连接线程,唯一的办法是使用POSIX线程。POSIX默认创建的线程是可连接的。为了把线程标记为脱离的或可连接的,使用pthread_attr_setdetachstate函数来修改正在创建的线程的属性。在线程启动后,你可以通过调用pthread_detach函数来把线程修改为可连接的。关于更多POSIX线程函数信息,参与pthread主页。关于更多如果连接一个线程,参阅pthread_join的主页。

1.2.4    设置线程的优先级

你创建的任何线程默认的优先级是和你本身线程相同。内核调度算法在决定该运行那个线程时,把线程的优先级作为考量因素,较高优先级的线程会比较低优先级的线程具有更多的运行机会。较高优先级不保证你的线程具体执行的时间,只是相比较低优先级的线程,它更有可能被调度器选择执行而已。

重要:让你的线程处于默认优先级值是一个不错的选择。增加某些线程的优先级,同时有可能增加了某些较低优先级线程的饥饿程度。如果你的应用程序包含较高优先级和较低优先级线程,而且它们之间必须交互,那么较低优先级的饥饿状态有可能阻塞其他线程,并造成性能瓶颈。

如果你想改变线程的优先级,Cocoa和POSIX都提供了一种方法来实现。对于Cocoa线程而言,你可以使用NSThread的setThreadPriority:类方法来设置当前运行线程的优先级。对于POSIX线程,你可以使用pthread_setschedparam函数来实现。关于更多信息,参与NSThread Class Reference或pthread_setschedparam主页。

1.3        编写你线程的主体入口点

对于大部分而言,Mac OS X上面线程结构的主体入口点和其他平台基本一样。你需要初始化你的数据结构,做一些工作或可行的设置一个run loop,并在线程代码被执行完后清理它。根据设计,当你写的主体入口点的时候有可能需要采取一些额外的步骤。

1.3.1    创建一个自动释放池(Autorelease Pool)

在Objective – C框架链接的应用程序,通常在它们的每一个线程必须创建至少一个自动释放池。如果应用程序使用管理模型,即应用程序处理的retain和release对象,那么自动释放池捕获任何从该线程autorelease的对象。

如果应用程序使用的垃圾回收机制,而不是管理的内存模型,那么创建一个自动释放池不是绝对必要的。在垃圾回收的应用程序里面,一个自动释放池是无害的,而且大部分情况是被忽略。允许通过个代码管理必须同时支持垃圾回收和内存管理模型。在这种情况下,内存管理模型必须支持自动释放池,当应用程序运行垃圾回收的时候,自动释放池只是被忽略而已。

如果你的应用程序使用内存管理模型,在你编写线程主体入口的时候第一件事情就是创建一个自动释放池。同样,在你的线程最后应该销毁该自动释放池。该池保证自动释放。虽然对象被调用,但是它们不被release直到线程退出。列表2-2显示了线程主体入口使用自动释放池的基本结构。

Listing 2-2  Defining your thread entry point routine

1
2
3
4
5
6
- (void)myThreadMainRoutine
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Top-level pool
    // Do thread work here.
    [pool release];  // Release the objects in the pool.
}

因为高级的自动释放池不会释放它的对象直到线程退出。长时运行的线程需求新建额外的自动释放池来更频繁的释放它的对象。比如,一个使用run loop的线程可能在每次运行完一次循环的时候创建并释放该自动释放池。更频繁的释放对象可以防止你的应用程序内存占用太大造成性能问题。虽然对于任何与性能相关的行为,你应该测量你代码的实际表现,并适当地调整使用自动释放池。

关于更多内存管理的信息和自动释放池,参阅“内存高级管理编程指南(Advanced Memory Management Programming Guide)”。

1.3.2    设置异常处理

如果你的应用程序捕获并处理异常,那么你的线程代码应该时刻准备捕获任何可能发生的异常。虽然最好的办法是在异常发生的地方捕获并处理它,但是如果在你的线程里面捕获一个抛出的异常失败的话有可能造成你的应用程序强退。在你线程的主体入口点安装一个try/catch模块,可以让你捕获任何未知的异常,并提供一个合适的响应。

当在Xcode构建你项目的时候,你可以使用C++或者Objective-C的异常处理风格。 关于更多设置如何在Objective-C里面抛出和捕获异常的信息,参阅Exception Programming Topics。

1.3.3    设置一个Run Loop

当你想编写一个独立运行的线程时,你有两种选择。第一种选择是写代码作为一个长期的任务,很少甚至不中断,线程完成的时候退出。第二种选择是把你的线程放入一个循环里面,让它动态的处理到来的任务请求。第一种方法不需要在你的代码指定任何东西;你只需要启动的时候做你打算做的事情即可。然而第二种选择需要在你的线程里面添加一个run loop。

Mac OS X和iOS提供了在每个线程实现run loop内置支持。Cocoa、Carbon和UIKit自动在你应用程序的主线程启动一个run loop,但是如果你创建任何辅助线程,你必须手工的设置一个run loop并启动它。

关于更多使用和配置run loop的信息,参阅“Run Loops”部分。

1.4        中断线程

退出一个线程推荐的方法是让它在它主体入口点正常退出。经管Cocoa、POSIX和Multiprocessing Services提供了直接杀死线程的例程,但是使用这些例程是强烈不鼓励的。杀死一个线程阻止了线程本身的清理工作。线程分配的内存可能造成泄露,并且其他线程当前使用的资源可能没有被正确清理干净,之后造成潜在的问题。

如果你的应用程序需要在一个操作中间中断一个线程,你应该设计你的线程响应取消或退出的消息。对于长时运行的操作,这意味着周期性停止工作来检查该消息是否到来。如果该消息的确到来并要求线程退出,那么线程就有机会来执行任何清理和退出工作;否则,它返回继续工作和处理下一个数据块。

响应取消消息的一个方法是使用run loop的输入源来接收这些消息。列表2-3显示了该结构的类似代码在你的线程的主体入口里面是怎么样的(该示例显示了主循环部分,不包括设立一个自动释放池或配置实际的工作步骤)。该示例在run loop上面安装了一个自定义的输入源,它可以从其他线程接收消息。关于更多设置输入源的信息,参阅“配置Run Loop源”。执行工作的总和的一部分后,线程运行的run loop来查看是否有消息抵达输入源。如果没有,run loop立即退出,并且循环继续处理下一个数据块。因为该处理器并没有直接的访问exitNow局部变量,退出条件是通过线程的字典来传输的。

Listing 2-3  Checking for an exit condition during a long job

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- (void)threadMainRoutine
{
    BOOL moreWorkToDo = YES;
    BOOL exitNow = NO;
    NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
    // Add the exitNow BOOL to the thread dictionary.
    NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary];
    [threadDict setValue:[NSNumber numberWithBool:exitNow] forKey:@"ThreadShouldExitNow"];
    // Install an input source.
    [self myInstallCustomInputSource];
    while (moreWorkToDo && !exitNow)
    {
        // Do one chunk of a larger body of work here.
        // Change the value of the moreWorkToDo Boolean when done.
        // Run the run loop but timeout immediately if the input source isn't waiting to fire.
        [runLoop runUntilDate:[NSDate date]];
        // Check to see if an input source handler changed the exitNow value.
        exitNow = [[threadDict valueForKey:@"ThreadShouldExitNow"] boolValue];
    }
}

iOS多线程编程指南(一)关于多线程编程

第一章      关于多线程编程

多年来,计算机的最大性能主要受限于它的中心微处理器的速度。然而由于个别处理器已经开始达到它的瓶颈限制,芯片制造商开始转向多核设计,让计算机具有了同时执行多个任务的能力。尽管Mac OS X利用了这些核心优势,在任何时候可以执行系统相关的任务,但自己的应用程序也可以通过多线程方法利用这些优势。

1.1        什么是多线程

多线程是一个比较轻量级的方法来实现单个应用程序内多个代码执行路径。在系统级别内,程序并排执行,系统分配到每个程序的执行时间是基于该程序的所需时间和其他程序的所需时间来决定的。然而在每个应程序的内部,存在一个或多个执行线程,它同时或在一个几乎同时发生的方式里执行不同的任务。系统本身管理这些执行的线程,调度它们在可用的内核上运行,并在需要让其他线程执行的时候抢先打断它们。

从技术角度来看,一个线程就是一个需要管理执行代码的内核级和应用级数据结构组合。内核级结构协助调度线程事件,并抢占式调度一个线程到可用的内核之上。应用级结构包括用于存储函数调用的调用堆栈和应用程序需要管理和操作线程属性和状态的结构。

在非并发的应用程序,只有一个执行线程。该线程开始和结束于你应用程序的main循环,一个个方法和函数的分支构成了你整个应用程序的所有行为。与此相反,支持并发的应用程序开始可以在需要额外的执行路径时候创建一个或多个线程。每个新的执行路径有它自己独立于应用程序main循环的定制开始循环。在应用程序中存在多个线程提供了两个非常重要的的潜在优势:

  1. 多个线程可以提高应用程序的感知响应。
  2. 多个线程可以提高应用程序在多核系统上的实时性能。

如果你的应用程序只有单独的线程,那么该独立程序需要完成所有的事情。它必须对事件作出响应,更新您的应用程序的窗口,并执行所有实现你应用程序行为需要的计算。拥有单独线程的主要问题是在同一时间里面它只能执行一个任务。那么当你的应用程序需要很长时间才能完成的时候会发生什么呢?当你的代码忙于计算你所需要的值的时候,你的程序就会停止响应用户事件和更新它的窗口。如果这样的情况持续足够长的时间,用户就会误认为你的程序被挂起了,并试图强制退出。如果你把你的计算任务转移到一个独立的线程里面,那么你的应用程序主线程就可以自由并及时响应用户的交互。

当然多线程并不是解决程序性能问题的灵丹妙药。多线程带来好处同时也伴随着潜在问题。应用程序内拥有多个可执行路径,会给你的代码增加更多的复杂性。每个线程需要和其他线程协调其行为,以防止它破坏应用程序的状态信息。因为应用程序内的多个线程共享内存空间,它们访问相同的数据结构。如果两个线程试图同时处理相同的数据结构,一个线程有可能覆盖另外线程的改动导致破坏该数据结构。即使有适当的保护,你仍然要注意由于编译器的优化导致给你代码产生很微妙的(和不那么微妙)的Bug。

1.2        线程术语

在讨论多线程和它支持的相关技术之前,我们有必要先了解一些基本的术语。如果你熟悉Carbon的多处理器服务API或者UNIX系统的话,你会发现本文档里面“任务(task)”被用于不同的定义。在Mac OS的早期版本,术语“任务(task)”是用来区分使用多处理器服务创建的线程和使用Carbon线程管理API创建的线程。在UNIX系统里面,术语“任务(task)”也在一段时间内被用于指代运行的进程。在实际应用中,多处理器服务任务是相当于抢占式的线程。

由于Carbon线程管理器和多处理器服务API是Mac OS X的传统技术,本文件采用下列术语:

  1. 线程(线程)用于指代独立执行的代码段。
  2. 进程(process)用于指代一个正在运行的可执行程序,它可以包含多个线程。
  3. 任务(task)用于指代抽象的概念,表示需要执行工作。

 

 

1.3        多线程的替代方法

你自己创建多线程代码的一个问题就是它会给你的代码带来不确定性。多线程是一个相对较低的水平和复杂的方式来支持你的应用程序并发。如果你不完全理解你的设计选择的影响,你可能很容易遇到同步或定时问题,其范围可以从细微的行为变化到严重到让你的应用程序崩溃并破坏用户数据。

你需要考虑的另一个因素是你是否真的需要多线程或并发。多线程解决了如何在同一个进程内并发的执行多路代码路径的问题。然而在很多情况下你是无法保证你所在做的工作是并发的。多线程引入带来大量的开销,包括内存消耗和CPU占用。你会发现这些开销对于你的工作而言实在太大,或者有其他方法会更容易实现。

表1-1列举了多线程的替代方法。该表包含了多线程的替代技术(比如操作对象和GCD)和如何更高效的使用单个线程。

Table 1-1  Alternative technologies to threads

Technology Description
Operation objects Introduced in Mac OS X v10.5, an operation object is a wrapper for a task that would normally be executed on a secondary thread. This wrapper hides the thread management aspects of performing the task, leaving you free to focus on the task itself. You typically use these objects in conjunction with an operation queue object, which actually manages the execution of the operation objects on one more threads.
For more information on how to use operation objects, see Concurrency Programming Guide.

Grand Central Dispatch (GCD)

Introduced in Mac OS x v10.6, Grand Central Dispatch is another alternative to threads that lets you focus on the tasks you need to perform rather than on thread management. With GCD, you define the task you want to perform and add it to a work queue, which handles the scheduling of your task on an appropriate thread. Work queues take into account the number of available cores and the current load to execute your tasks more efficiently than you could do yourself using threads.
For information on how to use GCD and work queues, see Concurrency Programming Guide
Idle-time notifications For tasks that are relatively short and very low priority, idle time notifications let you perform the task at a time when your application is not as busy. Cocoa provides support for idle-time notifications using the NSNotificationQueue object. To request an idle-time notification, post a notification to the default NSNotificationQueue object using the NSPostWhenIdle option. The queue delays the delivery of your notification object until the run loop becomes idle. For more information, see Notification Programming Topics.
Asynchronous functions The system interfaces include many asynchronous functions that provide automatic concurrency for you. These APIs may use system daemons and processes or create custom threads to perform their task and return the results to you. (The actual implementation is irrelevant because it is separated from your code.) As you design your application, look for functions that offer asynchronous behavior and consider using them instead of using the equivalent synchronous function on a custom thread.
Timers You can use timers on your application’s main thread to perform periodic tasks that are too trivial to require a thread, but which still require servicing at regular intervals. For information on timers, see “Timer Sources.”
Separate processes Although more heavyweight than threads, creating a separate process might be useful in cases where the task is only tangentially related to your application. You might use a process if a task requires a significant amount of memory or must be executed using root privileges. For example, you might use a 64-bit server process to compute a large data set while your 32-bit application displays the results to the user.

注意:当使用fork函数加载独立进程的时候,你必须总是在fork后面调用exec或者类似的函数。基于Core Foundation、Cocao或者Core Data框架(无论显式还是隐式关联)的应用程序随后调用exec函数或者类似的函数都会导出不确定的结果。

1.4        线程支持

如果你已经有代码使用了多线程,Mac OS X和iOS提供几种技术来在你的应用程序里面创建多线程。此外,两个系统都提供了管理和同步你需要在这些线程里面处理的工作。以下几个部分描述了一些你在Mac OS X和iOS上面使用多线程的时候需要注意的关键技术。

1.4.1    线程包

虽然多线程的底层实现机制是Mach的线程,你很少(即使有)使用Mach级的线程。相反,你会经常使用到更多易用的POSIX 的API或者它的衍生工具。Mach的实现没有提供多线程的基本特征,但是包括抢占式的执行模型和调度线程的能力,所以它们是相互独立的。

列表1-2列举你可以在你的应用程序使用的线程技术。

Table 1-2  Thread technologies

Technology Description
Cocoa threads Cocoa implements threads using the NSThread class. Cocoa also provides methods on NSObject for spawning new threads and executing code on already-running threads. For more information, see “Using NSThread” and “Using NSObject to Spawn a Thread.”
POSIX threads POSIX threads provide a C-based interface for creating threads. If you are not writing a Cocoa application, this is the best choice for creating threads. The POSIX interface is relatively simple to use and offers ample flexibility for configuring your threads. For more information, see “Using POSIX Threads”
Multiprocessing Services Multiprocessing Services is a legacy C-based interface used by applications transitioning from older versions of Mac OS. This technology is available in Mac OS X only and should be avoided for any new development. Instead, you should use the NSThread class or POSIX threads. If you need more information on this technology, see Multiprocessing Services Programming Guide.

在应用层上,其他平台一样所有线程的行为本质上是相同的。线程启动之后,线程就进入三个状态中的任何一个:运行(running)、就绪(ready)、阻塞(blocked)。如果一个线程当前没有运行,那么它不是处于阻塞,就是等待外部输入,或者已经准备就绪等待分配CPU。线程持续在这三个状态之间切换,直到它最终退出或者进入中断状态。

当你创建一个新的线程,你必须指定该线程的入口点函数(或Cocoa线程时候为入口点方法)。该入口点函数由你想要在该线程上面执行的代码组成。但函数返回的时候,或你显式的中断线程的时候,线程永久停止,且被系统回收。因为线程创建需要的内存和时间消耗都比较大,因此建议你的入口点函数做相当数量的工作,或建立一个运行循环允许进行经常性的工作。

为了获取更多关于线程支持的可用技术并且如何使用它们,请阅读“线程管理部分”。

1.4.2    Run Loops

注:为了便于记忆,文本后面部分翻译Run Loops的时候基本采用原义,而非翻译为“运行循环”。

一个run loop是用来在线程上管理事件异步到达的基础设施。一个run loop为线程监测一个或多个事件源。当事件到达的时候,系统唤醒线程并调度事件到run loop,然后分配给指定程序。如果没有事件出现和准备处理,run loop把线程置于休眠状态。

你创建线程的时候不需要使用一个run loop,但是如果你这么做的话可以给用户带来更好的体验。Run Loops可以让你使用最小的资源来创建长时间运行线程。因为run loop在没有任何事件处理的时候会把它的线程置于休眠状态,它消除了消耗CPU周期轮询,并防止处理器本身进入休眠状态并节省电源。

为了配置run loop,你所需要做的是启动你的线程,获取run loop的对象引用,设置你的事件处理程序,并告诉run loop运行。Cocoa和Carbon提供的基础设施会自动为你的主线程配置相应的run loop。如果你打算创建长时间运行的辅助线程,那么你必须为你的线程配置相应的run loop。

关于run loops的详细信息和如何使用它们的例子会在“Run Loops”部分介绍。

1.4.3    同步工具

线程编程的危害之一是在多个线程之间的资源争夺。如果多个线程在同一个时间试图使用或者修改同一个资源,就会出现问题。缓解该问题的方法之一是消除共享资源,并确保每个线程都有在它操作的资源上面的独特设置。因为保持完全独立的资源是不可行的,所以你可能必须使用锁,条件,原子操作和其他技术来同步资源的访问。

锁提供了一次只有一个线程可以执行代码的有效保护形式。最普遍的一种锁是互斥排他锁,也就是我们通常所说的“mutex”。当一个线程试图获取一个当前已经被其他线程占据的互斥锁的时候,它就会被阻塞直到其他线程释放该互斥锁。系统的几个框架提供了对互斥锁的支持,虽然它们都是基于相同的底层技术。此外Cocoa提供了几个互斥锁的变种来支持不同的行为类型,比如递归。获取更多关于锁的种类的信息,请阅读“锁”部分内容。

除了锁,系统还提供了条件,确保在你的应用程序任务执行的适当顺序。一个条件作为一个看门人,阻塞给定的线程,直到它代表的条件变为真。当发生这种情况的时候,条件释放该线程并允许它继续执行。POSIX级别和基础框架都直接提供了条件的支持。(如果你使用操作对象,你可以配置你的操作对象之间的依赖关系的顺序确定任务的执行顺序,这和条件提供的行为非常相似)。

尽管锁和条件在并发设计中使用非常普遍,原子操作也是另外一种保护和同步访问数据的方法。原子操作在以下情况的时候提供了替代锁的轻量级的方法,其中你可以执行标量数据类型的数学或逻辑运算。原子操作使用特殊的硬件设施来保证变量的改变在其他线程可以访问之前完成。

获取更多关于可用同步工具信息,请阅读“同步工具”部分。

1.4.4    线程间通信

虽然一个良好的设计最大限度地减少所需的通信量,但在某些时候,线程之间的通信显得十分必要。(线程的任务是为你的应用程序工作,但如果从来没有使用过这些工作的结果,那有什么好处呢?)线程可能需要处理新的工作要求,或向你应用程序的主线程报告其进度情况。在这些情况下,你需要一个方式来从其他线程获取信息。幸运的是,线程共享相同的进程空间,意味着你可以有大量的可选项来进行通信。

线程间通信有很多种方法,每种都有它的优点和缺点。“配置线程局部存储”列出了很多你可以在Mac OS X上面使用的通信机制。(异常的消息队列和Cocoa分布式对象,这些技术也可在iOS用来通信)。本表中的技术是按照复杂性的顺序列出。

Table 1-3  Communication mechanisms

Mechanism Description
Direct messaging Cocoa applications support the ability to perform selectors directly on other threads. This capability means that one thread can essentially execute a method on any other thread. Because they are executed in the context of the target thread, messages sent this way are automatically serialized on that thread. For information about input sources, see “Cocoa Perform Selector Sources.”
Global variables, shared memory, and objects Another simple way to communicate information between two threads is to use a global variable, shared object, or shared block of memory. Although shared variables are fast and simple, they are also more fragile than direct messaging. Shared variables must be carefully protected with locks or other synchronization mechanisms to ensure the correctness of your code. Failure to do so could lead to race conditions, corrupted data, or crashes.
Conditions Conditions are a synchronization tool that you can use to control when a thread executes a particular portion of code. You can think of conditions as gate keepers, letting a thread run only when the stated condition is met. For information on how to use conditions, see “Using Conditions.”
Run loop sources A custom run loop source is one that you set up to receive application-specific messages on a thread. Because they are event driven, run loop sources put your thread to sleep automatically when there is nothing to do, which improves your thread’s efficiency. For information about run loops and run loop sources, see “Run Loops.”
Ports and sockets Port-based communication is a more elaborate way to communication between two threads, but it is also a very reliable technique. More importantly, ports and sockets can be used to communicate with external entities, such as other processes and services. For efficiency, ports are implemented using run loop sources, so your thread sleeps when there is no data waiting on the port. For information about run loops and about port-based input sources, see “Run Loops.”
Message queues The legacy Multiprocessing Services defines a first-in, first-out (FIFO) queue abstraction for managing incoming and outgoing data. Although message queues are simple and convenient, they are not as efficient as some other communications techniques. For more information about how to use message queues, see Multiprocessing Services Programming Guide.
Cocoa distributed objects Distributed objects is a Cocoa technology that provides a high-level implementation of port-based communications. Although it is possible to use this technology for inter-thread communication, doing so is highly discouraged because of the amount of overhead it incurs. Distributed objects is much more suitable for communicating with other processes, where the overhead of going between processes is already high. For more information, seeDistributed Objects Programming Topics.

1.1        设计技巧

以下各节帮助你实现自己的线程提供了指导,以确保你代码的正确性。部分指南同时提供如何利用你的线程代码获得更好的性能。任何性能的技巧,你应该在你更改你代码之前、期间、之后总是收集相关的性能统计数据。

1.1.1    避免显式创建线程

手动编写线程创建代码是乏味的,而且容易出现错误,你应该尽可能避免这样做。Mac OS X和iOS通过其他API接口提供了隐式的并发支持。你可以考虑使用异步API,GCD方式,或操作对象来实现并发,而不是自己创建一个线程。这些技术背后为你做了线程相关的工作,并保证是无误的。此外,比如GCD和操作对象技术被设计用来管理线程,比通过自己的代码根据当前的负载调整活动线程的数量更高效。 关于更多GCD和操作对象的信息,你可以查阅“并发编程指南(Concurrency Programming Guid)”。

1.1.2    保持你的线程合理的忙

如果你准备人工创建和管理线程,记得多线程消耗系统宝贵的资源。你应该尽最大努力确保任何你分配到线程的任务是运行相当长时间和富有成效的。同时你不应该害怕中断那些消耗最大空闲时间的线程。线程使用一个平凡的内存量,它的一些有线,所以释放一个空闲线程,不仅有助于降低您的应用程序的内存占用,它也释放出更多的物理内存使用的其他系统进程。线程占用一定量的内存,其中一些是有线的,所以释放空闲线程不但帮助你减少了你应用程序的内存印记,而且还能释放出更多的物理内存给其他系统进程使用。

重要:在你中断你的空闲线程开始之前,你必须总是记录你应用程序当前的性能基线测量。当你尝试修改后,采取额外的测量来确保你的修改实际上提高了性能,而不是对它操作损害。

1.1.3    避免共享数据结构

避免造成线程相关资源冲突的最简单最容易的办法是给你应用程序的每个线程一份它需求的数据的副本。当最小化线程之间的通信和资源争夺时并行代码的效果最好。

创建多线程的应用是很困难的。即使你非常小心,并且在你的代码里面所有正确的地方锁住共享资源,你的代码依然可能语义不安全的。比如,当在一个特定的顺序里面修改共享数据结构的时候,你的代码有可能遇到问题。以原子方式修改你的代码,来弥补可能随后对多线程性能产生损耗的情况。把避免资源争夺放在首位通常可以得到简单的设计同样具有高性能的效果。

1.1.4    多线程和你的用户界面

如果你的应用程序具有一个图形用户界面,建议你在主线程里面接收和界面相关的事件和初始化更新你的界面。这种方法有助于避免与处理用户事件和窗口绘图相关的同步问题。一些框架,比如Cocoa,通常需要这样操作,但是它的事件处理可以不这样做,在主线程上保持这种行为的优势在于简化了管理你应用程序用户界面的逻辑。

有几个显著的例外,它有利于在其他线程执行图形操作。比如,QuickTime API包含了一系列可以在辅助线程执行的操作,包括打开视频文件,渲染视频文件,压缩视频文件,和导入导出图像。类似的,在Carbon和Cocoa里面,你可以使用辅助线程来创建和处理图片和其他图片相关的计算。使用辅助线程来执行这些操作可以极大提高性能。如果你不确定一个操作是否和图像处理相关,那么你应该在主线程执行这些操作。

关于QuickTime线程安全的信息,查阅Technical Note TN2125:“QuickTime的线程安全编程”。关于Cocoa线程安全的更多信息,查阅“线程安全总结”。关于Cocoa绘画信息,查阅Cocoa绘画指南(Cocoa Drawing Guide)。

1.1.5    了解线程退出时的行为

进程一直运行直到所有非独立线程都已经退出为止。默认情况下,只有应用程序的主线程是以非独立的方式创建的,但是你也可以使用同样的方法来创建其他线程。当用户退出程序的时候,通常考虑适当的立即中断所有独立线程,因为通常独立线程所做的工作都是是可选的。如果你的应用程序使用后台线程来保存数据到硬盘或者做其他周期行的工作,那么你可能想把这些线程创建为非独立的来保证程序退出的时候不丢失数据。

以非独立的方式创建线程(又称作为可连接的)你需要做一些额外的工作。因为大部分上层线程封装技术默认情况下并没有提供创建可连接的线程,你必须使用POSIX API来创建你想要的线程。此外,你必须在你的主线程添加代码,来当它们最终退出的时候连接非独立的线程。更多有关创建可连接的线程信息,请查阅“设置线程的脱离状态”部分。

如果你正在编程Cocoa的程序,你也可以通过使用applicationShouldTerminate:的委托方法来延迟程序的中断直到一段时间后或者完成取消。当延迟中断的时候,你的程序需要等待直到任何周期线程已经完成它们的任务且调用了replyToApplicationShouldTerminate:方法。关于更多这些方法的信息,请查阅NSApplication Class Reference。

1.1.6    处理异常

当抛出一个异常时,异常的处理机制依赖于当前调用堆栈执行任何必要的清理。因为每个线程都有它自己的调用堆栈,所以每个线程都负责捕获它自己的异常。如果在辅助线程里面捕获一个抛出的异常失败,那么你的主线程也同样捕获该异常失败:它所属的进程就会中断。你无法捕获同一个进程里面其他线程抛出的异常。

如果你需要通知另一个线程(比如主线程)当前线程中的一个特殊情况,你应该捕捉异常,并简单地将消息发送到其他线程告知发生了什么事。根据你的模型和你正在尝试做的事情,引发异常的线程可以继续执行(如果可能的话),等待指示,或者干脆退出。

注意:在Cocoa里面,一个NSException对象是一个自包含对象,一旦它被引发了,那么它可以从一个线程传递到另外一个线程。

在一些情况下,异常处理可能是自动创建的。比如,Objective-C中的@synchronized包含了一个隐式的异常处理。

1.1.7    干净地中断你的线程

线程自然退出的最好方式是让它达到其主入口结束点。虽然有不少函数可以用来立即中断线程,但是这些函数应仅用于作为最后的手段。在线程达到它自然结束点之前中断一个线程阻碍该线程清理完成它自己。如果线程已经分配了内存,打开了文件,或者获取了其他类型资源,你的代码可能没办法回收这些资源,结果造成内存泄漏或者其他潜在的问题。

关于更多正确退出线程的信息,请查阅“中断线程”部分。

1.1.8    线程安全的库

虽然应用程序开发人员控制应用程序是否执行多个线程,类库的开发者则无法这样控制。当开发类库时,你必须假设调用应用程序是多线程,或者多线程之间可以随时切换。因此你应该总是在你的临界区使用锁功能。

对类库开发者而言,只当应用程序是多线程的时候才创建锁是不明智的。如果你需要锁定你代码中的某些部分,早期应该创建锁对象给你的类库使用,更好是显式调用初始化类库。虽然你也可以使用静态库的初始化函数来创建这些锁,但是仅当没有其他方式的才应该这样做。执行初始化函数需要延长加载你类库的时间,且可能对你程序性能造成不利影响。

注意:永远记住在你的类库里面保持锁和释放锁的操作平衡。你应该总是记住锁定类库的数据结构,而不是依赖调用的代码提供线程安全环境。

如果你真正开发Cocoa的类库,那么当你想在应用程序变成多线程的时候收到通知的话,你可以给NSWillBecomeMultiThreadedNotification 注册一个观察者。不过你不应用依赖于这些收到的通知,因为它们可能在你的类库被调用之前已经被发出了。

Linux系统中多线程程序编译makefile文件实例

在Linux系统下,编译一个多线程的程序命令为:

gcc -lpthread -o file file.c

在Linux下,用交叉编译器编译一个在uclinux下运行的多线程程序,makefile文件如下:

CFLAGS = -Wall -Os -Dlinux -D__linux__ -Dunix -D__uClinux__ -DEMBED

LDFLAGS =-Wl,-elf2flt

LOCAL_LIBS = /usr/local/arm-elf/lib/libpthread.a

CC = arm-elf-gcc

LD = arm-elf-gcc

TARGT = test

OBJ = $(TARGT).o

SRC = $(TARGT).c

all: $(TARGT)

%.o:%.c

$(CC) $(CFLAGS) -c $< -o $@

android的sqlite多线程 crash问题

工作发现sqlite在多线程中处理不好,经常crash。[[BR]]
写了个测试项目。
分别测试三种case: 直接使用,加锁,加引用。

并发1000个线程,做插入。

  • 1.直接使用,不加任何处理
    crash。完全没法用。08-06 12:07:04.323: E/AndroidRuntime(18482): java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed.
  • 2.使用锁
    执行正常,数据插入正常,没有sql句柄异常。结果OK。但是耗时26s.
  • 3.使用引用,无锁。
    起第758个线程时,crash.内存溢出。

改为并发100个线程,实际上客户端同一时间最好控制在20线程以内。

  • 1.直接使用,不加任何处理
    还是crash。完全没法用。{{{
    08-06 12:07:04.323: E/AndroidRuntime(18482): java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed.
    }}}
  • 2.使用锁
    执行正常,数据插入正常,没有sql句柄异常。结果OK。但是耗时2.2s.
  • 3.使用引用,无锁。
    执行正常,数据插入正常,没有sql句柄异常。结果OK。但是耗时1.0s.

结论:

实际使用中,用引用效率最高,考虑到实际线程不会太多,足够使用。[[BR]]
够严谨的话,得使用加锁。但由于资源竞争,效率会降低一半。 [[BR]]
一般项目建议使用加引用的方式。

demo地址