线程-学习笔记

干扰线程执行的前后顺序——Thread类的join方法 经常听到这样一个面试题:如何保证多个线程的执行顺序? 答案有很多,比如使用CompletableFuture(JDK8新增),LookSupport工具类,wait和notify,还有这篇文章要写的Thread.join() 方法 Thread类的 join() 方法可以阻塞当前的线程,假设线程A中执行调用了线程B的join方法,那在线程B执行完之前,A线程是无法继续往下执行的,需要注意的是 等待调用了join() 方法的线程执行完后再执行,所以使用时不能自己调用自己的join方法,这样会导致线程自身 阅读详情

1.背景

  在传统的Unix模型中,当一个进程需要由另一个实体执行某件事时,该进程派生(fork)一个子进程,让子进程去进行处理。Unix下的大多数网络服务器程序都是这么编写的,即父进程接受连接,派生子进程,子进程处理与客户的交互。

  虽然这种模型很多年来使用得很好,但是fork时有一些问题:

  • fork是昂贵的。内存映像要从父进程拷贝到子进程,所有描述字要在子进程中复制等等。目前有的Unix实现使用一种叫做写时拷贝(copy-on-write)的技术,可避免父进程数据空间向子进程的拷贝。尽管有这种优化技术,fork仍然是昂贵的。
  • fork子进程后,需要用进程间通信(IPC)在父子进程之间传递信息。Fork之前的信息容易传递,因为子进程从一开始就有父进程数据空间及所有描述字的拷贝。但是从子进程返回信息给父进程需要做更多的工作。

  线程有助于解决这两个问题。线程有时被称为轻权进程(lightweight process),因为线程比进程“轻权”,一般来说,创建一个线程要比创建一个进程快10~100倍。

  一个进程中的所有线程共享相同的全局内存,这使得线程很容易共享信息,但是这种简易性也带来了同步问题。

  一个进程中的所有线程不仅共享全局变量,而且共享:进程指令、大多数数据、打开的文件(如描述字)、信号处理程序和信号处置、当前工作目录、用户ID和组ID。但是每个线程有自己的线程ID、寄存器集合(包括程序计数器和栈指针)、栈(用于存放局部变量和返回地址)、error、信号掩码、优先级。在Linux中线程编程符合Posix.1标准,称为Pthreads。所有的pthread函数都以pthread_开头。在调用它们前均要包括pthread.h头文件,一个函数库libpthread实现。

2.一些操作的介绍

2.1相关数据结构

[ pthread_t ] :线程的ID;

[ pthread_attr_t ] :线程的属性;

2.2相关的操作函数

函数名作用
pthread_create()创建一个线程
pthread_exit()终止当前线程
pthread_cancel()中断另外一个线程的运行
pthread_join()阻塞当前的线程,直到另外一个线程运行结束
pthread_attr_init()初始化线程的属性
pthread_attr_setdetachstate()设置脱离状态的属性(决定这个线程在终止时是否可以被结合)
pthread_attr_getdetachstate()获取脱离状态的属性
pthread_attr_destroy()删除线程的属性
pthread_kill()向线程发送一个信号

2.3

  • 互斥量
  • 读写锁
  • 添加变量
  • 自旋锁
  • 屏障

3.概念

  1. 线程的组成部分:
  • Thread ID 线程ID
  • 一组寄存器
  • Stack 栈
  • Policy 优先级和策略
  • Signal mask 信号屏蔽字
  • Errno 错误码
  • Thread-Specific Data 私有数据

4.函数介绍

4.1线程定义

  • [ pthread_t ] pthread_ID,用于标识一个线程,不能单纯看成整数,可能是结构体,与实现有关;
  • [ pthread_equal ] 函数用于比较两个pthread_t是否相等
#include <pthread.h>
  
int pthread_equal(pthread_t tid1,pthread_t tid2)
  • [ pthread_self ] 函数用于获得本线程的thread id
#include <pthread.h>

pthread _t pthread_self(void);

4.2线程的创建

  • [ pthread_create ] 创建线程调用
#include <pthread.h>
2  
3 int pthread_create(
4        pthread_t*restrict tidp,
5        constpthread_attr_t*restrict attr,
6        void*(*start_rtn)(void*),void*restrict arg);
  • pthread_t *restrict tidp:返回最后创建出来的Thread的Thread ID
  • const pthread_attr_t *restrict attr:指定线程的Attributes,后面会讲道,现在可以用NULL
  • void *(*start_rtn)(void *):指定线程函数指针,该函数返回一个void ,参数也为void
  • void *restrict arg:传入给线程函数的参数
  • 返回错误值。

  一个进程中的每个线程都由一个线程ID(thread ID)标识,其数据类型是pthread_t(常常是unsigned int)。如果新的线程创建成功,其ID将通过tid指针返回。

  每个线程都有很多属性:优先级、起始栈大小、是否应该是一个守护线程等等,当创建线程时,我们可通过初始化一个pthread_attr_t变量说明这些属性以覆盖缺省值。我们通常使用缺省值,在这种情况下,我们将attr参数说明为空指针。

  最后,当创建一个线程时,我们要说明一个它将执行的函数。线程以调用该函数开始,然后或者显式地终止(调用pthread_exit)或者隐式地终止(让该函数返回)。函数的地址由func参数指定,该函数的调用参数是一个指针arg,如果我们需要多个调用参数,我们必须将它们打包成一个结构,然后将其地址当作唯一的参数传递给起始函数。

  在func和arg的声明中,func函数取一个通用指针(void *)参数,并返回一个通用指针(void *),这就使得我们可以传递一个指针(指向任何我们想要指向的东西)给线程,由线程返回一个指针(同样指向任何我们想要指向的东西)。调用成功,返回0,出错时返回正Exxx值。

  pthread函数在出错的时候不会设置errno,而是直接返回错误值

  在Linux 系统下面,在老的内核中,由于Thread也被看作是一种特殊,可共享地址空间和资源的Process,因此在同一个Process中创建的不同 Thread具有不同的Process ID(调用getpid获得)。而在新的2.6内核之中,Linux采用了NPTL(Native POSIX Thread Library)线程模型(可以参考http://en.wikipedia.org/wiki/Native_POSIX_Thread_Library和http://www-128.ibm.com/developerworks/linux/library/l-threading.html?ca=dgr-lnxw07LinuxThreadsAndNPTL),在该线程模型下同一进程下不同线程调用getpid返回同一个PID。

  不能对创建的新线程和当前创建者线程的运行顺序作出任何假设;

4.3线程终止

  • 在线程函数中return;
  • 被同一进程中的另外的线程Cancel掉;
  • 线程调用pthread_exit函数;

4.3.1. pthread_exit和pthread_join函数的用法

  1. 线程A调用pthread_join(B, &rval_ptr),被Block,进入Detached状态(如果已经进入Detached状态,则pthread_join函数返回EINVAL)。如果对B的结束代码不感兴趣,rval_ptr可以传NULL。
  2. 线程B调用pthread_exit(rval_ptr),退出线程B,结束代码为rval_ptr。注意rval_ptr指向的内存的生命周期,不应该指向B的Stack中的数据。
  3. 线程A恢复运行,pthread_join函数调用结束,线程B的结束代码被保存到rval_ptr参数中去。如果线程B被Cancel,那么rval_ptr的值就是PTHREAD_CANCELLED。
    两个函数原型如下:
#include <pthread.h>
 
void pthread_exit(void*rval_ptr);
 
int pthread_join(pthread_t thread,void**rval_ptr);========

  该函数等待一个线程终止。把线程和进程相比,pthread_creat类似于fork,而 pthread_join类似于waitpid。我们必须要等待线程的tid,很可惜,我们没有办法等待任意一个线程结束。如果status指针非空,线程的返回值(一个指向某个对象的指针)将存放在status指向的位置。

4.3.2.thread_cancel函数

一个Thread可以要求另外一个Thread被Cancel,通过调用pthread_cancel函数:

#include <pthread.h>
 
void pthread_cancel(pthread_t tid)

  该函数会使指定线程如同调用了pthread_exit(PTHREAD_CANCELLED)。不过,指定线程可以选择忽略或者进行自己的处理,在后面会讲到。此外,该函数不会导致Block,只是发送Cancel这个请求。

4.3.3 线程清理处理函数

线程可以安排在它退出的时候,某些函数自动被调用,类似atexit()函数。需要调用如下函数:

#include <pthread.h>
 
void pthread_cleanup_push(void(*rtn)(void*),void*arg);
void pthread_cleanup_pop(int execute);

  这两个函数维护一个函数指针的Stack,可以把函数指针和函数参数值push/pop。执行的顺序则是从栈顶到栈底,也就是和push的顺序相反。

  在下面情况下pthread_cleanup_push所指定的thread cleanup handlers会被调用:
a. 调用pthread_exit
b. 相应cancel请求
c. 以非0参数调用pthread_cleanup_pop()。(如果以0调用pthread_cleanup_pop(),那么handler不会被调用

  有一个比较怪异的要求是,由于这两个函数可能由宏的方式来实现,因此这两个函数的调用必须得是在同一个Scope之中,并且配对,因为在pthread_cleanup_push的实现中可能有一个{,而 pthread_cleanup_pop可能有一个}。因此,一般情况下,这两个函数是用于处理意外情况用的,举例如下:

void*thread_func(void*arg)
{
    pthread_cleanup_push(cleanup,“handler”)
 
    // do something
 
    Pthread_cleanup_pop(0);
    return((void*)0);
}

注意:如果线程是通过从它的启动例程中返回而终止的话,他的清理处理程序就不会被调用;

4.3.4 进程函数与线程函数

进程原语线程原语描述
forkpthread_create创建新的控制流
exitpthread_exit退出已有的控制流
waitpidpthread_join等待控制流并获得结束代码
atexitpthread_cleanup_push注册在控制流退出时候被调用的函数
getpidpthread_self获得控制流的id
abortpthread_cancel请求非正常退出

4.3.5 分离线程

  缺省情况下,一个线程A的结束状态被保存下来直到pthread_join为该线程被调用过,也就是说即使线程A已经结束,只要没有线程B调用 pthread_join(A),A的退出状态则一直被保存。而当线程处于Detached状态之时,当线程退出的时候,其资源可以立刻被回收,那么这个退出状态也丢失了。在这个状态下,无法为该线程调用pthread_join函数。我们可以通过调用pthread_detach函数来使指定线程进入 Detach状态:

#include <pthread.h>
int pthread_detach(pthread_t tid);

  通过修改调用pthread_create函数的attr参数,我们可以指定一个线程在创建之后立刻就进入Detached状态。

5.线程同步

5.1互斥量:Mutex

  各个现成向同一个文件顺序写入数据,最后得到的结果是不可想象的。所以用互斥锁来保证一段时间内只有一个线程在执行一段代码。

5.1.1数据结构

  • [ pthread_mutex_t ] 互斥量,必现首先对他进行初始化;

5.1.1操作函数

  • 初始化与销毁
    pthread_mutex_t,必须被初始化为PTHREAD_MUTEX_INITIALIZER
    (用于静态分配的mutex,等价于 pthread_mutex_init(…, NULL))或者调用pthread_mutex_init。Mutex也应该用pthread_mutex_destroy来销毁。这两个函数原型如下:(attr的具体含义后面讨论)
#include <pthread.h>
 
int pthread_mutex_init(
       pthread_mutex_t*restrict mutex,
       constpthread_mutexattr_t*restrict attr)
 
int pthread_mutex_destroy(pthread_mutex_t*mutex);
  • 锁与解锁

  pthread_mutex_lock 用于Lock Mutex,如果Mutex已经被Lock,该函数调用会Block直到Mutex被Unlock,然后该函数会Lock Mutex并返回。pthread_mutex_trylock类似,只是当Mutex被Lock的时候不会Block,而是返回一个错误值EBUSY。
pthread_mutex_unlock则是unlock一个mutex。这三个函数原型如下:

#include <pthread.h>
 
int pthread_mutex_lock(pthread_mutex_t*mutex);
 
int pthread_mutex_trylock(pthread_mutex_t*mutex);
 
int pthread_mutex_unlock(pthread_mutex_t*mutex);
  • 例程
void reader_function (void);
void writer_function (void);
char buffer;
int buffer_has_item=0;
pthread_mutex_t mutex;
struct timespec delay;
void main (void)
{
    pthread_t reader = PTHREAD_MUTEX_INITIALIZER;
    pthread_t writer = PTHREAD_MUTEX_INITIALIZER;
    /* 定义延迟时间*/
    delay.tv_sec =2;
    delay.tv_nec =0;
    /* 用默认属性初始化一个互斥锁对象*/
    pthread_mutex_init (&mutex,NULL);
    pthread_create(&reader, pthread_attr_default,(void*)&reader_function), NULL);
    pthread_create(&writer, pthread_attr_default,(void*)&writer_function), NULL);
    while(1);
}
void writer_function (void){
    while(1){
        /* 锁定互斥锁*/
        pthread_mutex_lock (&mutex);
        if(buffer_has_item==0){
            buffer=make_new_item();
            buffer_has_item=1;
        }
        /* 打开互斥锁*/
        pthread_mutex_unlock(&mutex);
        pthread_delay_np(&delay);
    }
}
void reader_function(void){
    while(1){
        pthread_mutex_lock(&mutex);
        if(buffer_has_item==1){
            consume_item(buffer);
            buffer_has_item=0;
        }
        pthread_mutex_unlock(&mutex);
        pthread_delay_np(&delay);
    }
}

  需要注意的是在使用互斥锁的过程中很有可能会出现死锁:两个线程试图同时占用两个资源,并按不同的次序锁定相应的互斥锁,例如两个线程都需要锁定互斥锁1和互斥锁2,a线程先锁定互斥锁1,b 线程先锁定互斥锁2,这时就出现了死锁。此时我们可以使用函数 pthread_mutex_trylock,它是函数pthread_mutex_lock的非阻塞版本,当它发现死锁不可避免时,它会返回相应的信息,程序员可以针对死锁做出相应的处理。另外不同的互斥锁类型对死锁的处理不一样,但最主要的还是要程序员自己在程序设计注意这一点。

5.2读写锁:Reader-Writer Locks

  读写锁与互斥量类似,不过读写锁允许更高的并行性。互斥量要么是锁住状态要么不加锁状态,而且异常只有一个线程可以对其加锁,读写锁有三种状态:读模式下加锁状态,写模式下的加锁状态,不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。

5.2.1数据结构

  • [ pthread_rwlock_t ]

5.2.1读写锁创建与关闭

#include <pthread.h>
 
int pthread_rwlock_init(
       pthread_rwlock_t*restrict rwlock,
       constpthread_rwlockattr_t*restrict attr)
 
int pthread_rwlock_destroy(pthread_rwlock_t*rwlock);

5.2.1读写锁操作函数

#include <pthread.h>
 
int pthread_rwlock_rdlock(pthread_rwlock_t*rwlock);
 
int pthread_rwlock_wrlock(pthread_rwlock_t*rwlock);
 
int pthread_rwlock_unlock(pthread_rwlock_t*rwlock);
 
int pthread_rwlock_tryrdlock(pthread_rwlock_t*rwlock);
 
int pthread_rwlock_trywrlock(pthread_rwlock_t*rwlock);
  • [ pthread_rwlock_rdlock ] :获得读锁
  • [ pthread_rwlock_wrlock ] :获得写锁
  • [ pthread_rwlock_unlock ] :释放锁,不管是读锁还是写锁都是调用此函数

  注意具体实现可能对同时获得读锁的线程个数有限制,所以在调用 pthread_rwlock_rdlock的时候需要检查错误值,而另外两个pthread_rwlock_wrlock和 pthread_rwlock_unlock则一般不用检查,如果我们代码写的正确的话。

5.3 条件变量:Conditional Variable

  互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线程间的同步。

  • mutex体现的是一种竞争,我离开了,通知你进来。

  • cond体现的是一种协作,我准备好了,通知你开始吧。

  • 条件必须被Mutex保护起来(防止公共资源竞争):其次,条件本身就是公共资源,如流水线的状态,因此必须使用互斥锁在临界区内对条件进行保护。其次,pthread_cond_wait()操作实际上分为两步,第一步将线程挂在等待条件的线程列表上,然后对互斥锁解锁。如果不传入这个互斥锁,实际上流程变为:1.加锁 2.判断流水线状态 3.解锁 4.挂起线程,则在3与4间非原子,如果在3之后生产者线程生产了一个产品并进行了notify,而notify结束后这个消费者线程才挂起,则这个notify丢失了,只有生产者再生产一个产品才会唤醒它,如果只有一个消费者的话则这个notify完全丢失,如果有多个消费者则还可能被其他消费者所捕获,但逻辑上出现了问题。

5.3.1. 数据结构

  • [ pthread_cond_t ] :必须被初始化为PTHREAD_COND_INITIALIZER(用于静态分配的条件,等价于pthread_cond_init(…, NULL))或者调用pthread_cond_init。
#include <pthread.h>
 
int pthread_cond_init(
       pthread_cond_t*restrict cond,
       constpthread_condxattr_t*restrict attr)
 
int pthread_cond_destroy(pthread_cond_t*cond);

5.3.2等待与唤醒

  • [ pthread_cond_wait ] :函数用于等待条件发生(=true)。pthread_cond_timedwait类似,只是当等待超时的时候返回一个错误值ETIMEDOUT。超时的时间用timespec结构指定。此外,两个函数都需要传入一个Mutex用于保护条件。
#include <pthread.h>
 
int pthread_cond_wait(
       pthread_cond_t*restrict cond,
       pthread_mutex_t*restrict mutex);
 
int pthread_cond_timedwait(
       pthread_cond_t*restrict cond,
       pthread_mutex_t*restrict mutex,
       conststruct timespec *restrict timeout);
  • 例子
pthread_mutex_t count_lock;
pthread_cond_t count_nonzero;
unsigned count;
decrement_count (){
    pthread_mutex_lock (&count_lock);
    while(count==0)
        pthread_cond_wait(&count_nonzero,&count_lock);
    count=count -1;
    pthread_mutex_unlock (&count_lock);
}
increment_count(){
    pthread_mutex_lock(&count_lock);
    if(count==0)
        pthread_cond_signal(&count_nonzero);
    count=count+1;
    pthread_mutex_unlock(&count_lock);
}

  count 值为0时, decrement函数在pthread_cond_wait处被阻塞,并打开互斥锁count_lock。此时,当调用到函数 increment_count时,pthread_cond_signal()函数改变条件变量,告知decrement_count()停止阻塞。

  • 超时
  • [ timespec ]
struct timespec {
       time_t tv_sec;       /* seconds */
       long   tv_nsec;      /* nanoseconds */
};

  注意timespec的时间是绝对时间而非相对时间,因此需要先调用gettimeofday函数获得当前时间,再转换成timespec结构,加上偏移量。

  • 唤醒
#include <pthread.h>
 
int pthread_cond_signal(pthread_cond_t*cond);
 
int pthread_cond_broadcast(pthread_cond_t*cond);

  两者的区别是前者会唤醒单个线程,而后者会唤醒多个线程。唤醒单个线程是随机的。

5.4. 自旋锁

  自旋锁与互斥量类似,但是他不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等待(自旋)阻塞状态。自旋锁可用于以下情况:锁被持有的时间短,而且线程并不希望在重新调度上花费太多成本。在用户层,自旋锁并不是非常有用,除非运行在不允许抢占的实时调度类中。而且现在互斥类的实现非常的高效,以至于应用程序采用互斥锁或者自旋锁的性能基本差不多。

5.4.1数据结构

  • [ pthread_spinlock_t ]

5.4.2 自旋锁的创建与销毁

#include <pthread.h>
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
int pthread_spin_destroy(pthread_spinlock_t *lock);

  自旋锁的接口跟互斥量的接口类似,这使得它可以比较容易从一个替换为另一个。

5.4.3. 自旋锁的锁与解锁

int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);

5.5 屏障

  屏障是用户协调多个线程并行工作的同步机制;屏障允许每个线程等待,知道所有的合租线程都达到某点然后从该点继续执行。

5.5.1. 数据结构

  • [ pthread_barrier_t ]

5.5.2. 创建与销毁

#include<pthread.h>
int pthread_barrier_init(pthread_barrier_t *restrict barrier,
                        const pthread_barrierattr_t *restrict attr,
                        unsigned int count);
void pthread_barrier_destroy(pthread_barrier_t *barrier);
  • count:允许所有线程继续运行之前,必须到达的屏障的线程数目。
#include <pthread.h>
int pthread_barrier_wait(pthread_barrier_t *barrier);

  表明线程已经准备完工作,准备等所有线程赶上来;

  对任意线程,pthread_barrier_wait返回PTHREAD_BARRIER_SERIAL_THREAD.剩下的线程看到的返回值是0。这使得一个线程可以作为主线程,它可以工作其他所有线程已经完成的工作结果上。
  一旦达到屏障计数,而且线程处于非阻塞状态,屏障可以重用,而且计数不会改变,除非使用了pthread_barrier_destroy函数,并pthread_barrier_init重新设置的计数值。

6. 线程属性

6.1线程属性设置

  我们用pthread_create函数创建一个线程,在这个线程中,我们使用默认参数,即将该函数的第二个参数设为NULL。的确,对大多数程序来说,使用默认属性就够了,但我们还是有必要来了解一下线程的有关属性。
  属性结构为pthread_attr_t,它同样在头文件pthread.h中定义,属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。属性对象主要包括是否绑定、是否分离、
堆栈地址、堆栈大小、优先级。默认的属性为非绑定、非分离、缺省的堆栈、与父进程同样级别的优先级。

6.2. 绑定

  关于线程的绑定,牵涉到另外一个概念:轻进程(LWP:Light Weight Process)。轻进程可以理解为内核线程,它位于用户层和系统层之间。系统对线程资源的分配、对线程的控制是通过轻进程来实现的,一个轻进程可以控制一个或多个线程。默认状况下,启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制的,这种状况即称为非绑定的。绑定状况下,则顾名思义,即某个线程固定的"绑"在一个轻进程之上。被绑定的线程具有较高的响应速度,这是因为CPU时间片的调度是面向轻进程的,绑定的线程可以保证在需要的时候它总有一个轻进程可用。通过设置被绑定的轻进程的优先级和调度级可以使得绑定的线程满足诸如实时反应之类的要求。

  设置线程绑定状态的函数为 pthread_attr_setscope,它有两个参数,第一个是指向属性结构的指针,第二个是绑定类型,它有两个取值: PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)。下面的代码即创建了一个绑定的线程。

#include <pthread.h>
pthread_attr_t attr;
pthread_t tid;
/*初始化属性值,均设为默认值*/
pthread_attr_init(&attr);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
pthread_create(&tid,&attr,(void*) my_function, NULL);

6.3. 线程分离状态

  线程的分离状态决定一个线程以什么样的方式来终止自己。非分离的线程终止时,其线程ID和退出状态将保留,直到另外一个线程调用pthread_join.分离的线程在当它终止时,所有的资源将释放,我们不能等待它终止。
  设置线程分离状态的函数为:

pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)

  第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)或 PTHREAD _CREATE_JOINABLE(非分离线程)。

  这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在 pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用 pthread_cond_timewait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。设置一段等待时间,是在多线程编程里常用的方法。

6.4. 优先级

  它存放在结构sched_param中。用函数pthread_attr_getschedparam和函数 pthread_attr_setschedparam进行存放,一般说来,我们总是先取优先级,对取得的值修改后再存放回去。下面即是一段简单的例子。

#include <pthread.h>
#include <sched.h>
pthread_attr_t attr;pthread_t tid;
sched_param param;
int newprio=20;
/*初始化属性*/
pthread_attr_init(&attr);
/*设置优先级*/
pthread_attr_getschedparam(&attr,&param); 
param.sched_priority=newprio;
pthread_attr_setschedparam(&attr,&param);
pthread_create(&tid,&attr,(void*)myfunction, myarg);
Matlab系列之GUI设计基础 Matlab系列之GUI设计基础简介编辑界面菜单设计控件设计控件描述个人理解Matlab转译控件属性【1】外观与行为【2】控件对象的信息【3】回调函数【4】状态信息结束结束更多精彩,等你发现~ 简介 GUI即图形用户界面(Graphical User Interface),人机交互的一种方式,可以简化用户的操作,比如只需要点点窗口上的按键或者菜单选择等等,就可以实现一个复杂的功能,有点类似黑盒的操作吧。 本篇将主要介绍GUI的使用,以及一些功能,后续的篇章再使用GUI来设计实例。 打开GUI可以在Matla 阅读详情

相关推荐

DMR对讲机的基本概念理解

刚刚入手DMR对讲机,尚未成功联通。国内互联网上能找到的资料很少,大多数也都是一些DMR协议介绍之类的玩意,对实际操作没有指导意义。以下全部是这几天在网上研究各种资料得出的个人思考,如有错误请斧正。 相比与模拟通讯系统,DMR将语音信号转换为包的形式进行收发。将DMR对比成TCP/IP系统会更容易理解。 一般DMR对讲机都支持模数双通,说明DMR应该使用了跟FM同样的无线AP组件,只在解码上有区别。对讲机先使用DMR进行数字解码,解码成功则说明是数字信号,不成功则使用模拟方式再次进行解码。可以对等理解为

1万+

【Linux】第十篇:线程的创建,等待,终止与分离

目录1.线程理解线程与进程线程的特点线程异常Linux线程控制2.创建进程 —— pthread_create获取线程ID —— pthread_self代码示例实验1实验2实验33.进程ID和线程ID4.终止线程等待线程 —— pthread_join线程退出 return线程退出 pthread_exit实验线程退出 pthread_cancel取消点库函数 pthread_setcancelstate : 修改cancelstate库函数 pthread_setcanceltype : 修改cance

qq_43041053的博客 1170

攻克JESD204B多ADC同步:从时钟对齐到数据对齐的实战解析

本文深入解析了JESD204B多ADC同步的系统性挑战与实战解决方案。从确保设备时钟同源、精确捕获SYSREF信号,到管理接收端弹性缓冲区与释放点,文章提供了从时钟对齐到数据对齐的完整调试路径与排查清单,是攻克高速数据采集系统中多片ADC同步难题的实用指南。

weixin_29231725的博客 197

[多线程] - join方法的使用详解

文章目录一、前言二、join方法1. join的Api简介2. join方法的实现3.总结 一、前言 按照惯例,我们在讲一个知识点之前习惯性的提出一个业务场景的应用来帮助理解,今天咱们就通过一个渣男的案例来讲讲join的相关特性: 土木三班陈同学是一个很有名气的渣男,最近陈同学刚刚跟女朋友分了手,处于空窗期的他打开了微信附近的人,挑了个他觉得长得还不错的姑娘发起了搭讪请求。 public class ChatUp { // 被搭讪人是否回复 public volatile static boolea

xiaoai1994的博客 1915

格(Lattice)基础(一)

有关格的基础概念,和相关的格难题

Amire0x的小乐园 5699

FDTD脚本教程——结构的创建

文章目录结构的搭建创建长方体创建柱体多边形柱体(椭)圆柱体圆环柱自定义形状创建surface波导球棱锥 在lumerical中,可以使用脚本进行操作,可在右侧脚本编辑器运行,或者直接在下方shell输入。 结构的搭建 创建长方体 um = 1e-6; nm = 1e-7; addrect; set("name","myRect"); set("material","SiO2 (Glass) - Palik"); set("render type","wireframe"); # 默认是细节渲染,也就是det

战斗果冻的训练中心 2万+

线程的状态及join方法

线程运行状态包括新建状态、就绪状态、运行状态、阻塞状态及死亡状态。 New (新创建)   当用new操作符创建一个线程时,如new Thread(r),该线程还没有开始运行。这意味它的状态是new。此时程序还没有开始运行线程中的代码,在线程运行之前还有一些基础工作要做。 Runnable (可运行/就绪)   一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法(如图中1所示)即启动了线程,start()方法创建线程运行的系统资源,并调度

程序员杂谈 2020

安卓/苹果/支付宝sdk拉起支付宝h5/app支付

代码】安卓/苹果/支付宝sdk拉起支付宝h5/app支付。

m0_68973987的博客 5386

多线程中的Join

线程中join(强制执行):当前线程执行完毕之后,才会执行后面程序,其他线程阻塞; 实例: public class ThreadJoin implements Runnable{ @Override public void run() { for (int i = 1; i < 1000; i++) { System.out.println("Join..."+i+"运行中"); } } public stat

柯基的博客 3203

Claude Code 处理长任务 + Cursor 专注交互:双栈协作的 3 种分工场景与响应提速 35% 实测

揭示 Claude Code 与 Cursor 在 AI 编程中的本质分工:Claude Code 作为离线式架构师处理跨文件、带约束的长链路重构(如单体拆微服务),Cursor 作为现场交互工程师专注秒级局部编辑;提出按任务切片动态切换的三种工程化协作场景,并给出结构化上下文构建、约束型 Prom

weixin_28728443的博客 123

如何对TBSS的结果进行统计学的分析,已完成置换检验,如何在最终的TBSS输出结果提取除具体值及如何做进一步相关性分析??

🏆本文收录于 《全栈 Bug 调优(实战版)》 专栏。专栏聚焦真实项目中的各类疑难 Bug,从成因剖析 → 排查路径 → 解决方案 → 预防优化全链路拆解,形成一套可复用、可沉淀的实战知识体系。无论你是初入职场的开发者,还是负责复杂项目的资深工程师,都可以在这里构建一套属于自己的「问题诊断与性能调优」方法论,助你稳步进阶、放大技术价值。

**My Coding Family** 1441

java学习笔记——多线程

总结:1.程序、进程和线程的区分:程序(program):为完成特定任务,用某种语言编写的`一组指令的集合`。即指一段静态的代码。进程(process):程序的一次执行过程,或是正在内存中运行的应用程序。程序是静态的,进程是动态的。进程作为操作系统调度和分配资源的最小单位。线程(thread):进程可进一步细化为线程,是程序内部的一条执行路径线程.作为CPU调度和执行的最小单位2.线程调度策略分时调度:所有线程轮流使用`CPU`的使用权,并且平均分配每个线程占用CPU的时间。

qq_58956810的博客 1084

微信小程序 - request:fail errcode:10011 cronet_error_code:0 error_msg:network change, cancel all task请求报错

微信小程序请求接口,报request:fail errcode:10011是什么原因,uniapp微信小程序request:fail errcode:10011 cronet_error_code:0 error_msg:network change, cancel all task,微信小程序wx.request常规访问接口,有个别用户的接口请求频繁报错,UniApp 微信小程序uni.request请求接口报错:request:fail errcode:10011 的原因分析与解决方案,苹果ios,安卓

🏆 前端领域优质创作者 4567

Java线程学习笔记之Executor

引用:点击打开链接 Java线程学习笔记之Executor Uncategorized, by Zen. 并发编程的一种编程方式是把任务拆分为一些列的小任务,即Runnable,然后在提交给一个Executor执行,Executor在执行时使用内部的线程池完成操作。由此,任务提交者不需要再创建管理线程,使用更方便,也减少了开销。有两种任务:Runnable和Callable,Call

wtfpewfn的专栏 615

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Java多线程学习笔记

1.synchronized是关键字,Lock是接口;2.synchronized是隐式的加锁,lock是显式的加锁;3.synchronized可以作用于方法上,lock只能作用于方法块;4.synchronized底层采用的是objectMonitor,lock采用的AQS;5.synchronized是阻塞式加锁,lock是非阻塞式加锁支持可中断式加锁,支持超时时间的加锁;

2402_87028704的博客 712

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

线程小白的学习笔记

本篇为 Java编程的逻辑 的并发内容的学习笔记 什么是线程? 线程是表示一条单独执行的程序执行流,它有自己的程序计数器和栈。 创建线程 Java 中的 Thread对象 实现了 Runnable 接口,因此创建线程的方法有两种。 public class Thread implements Runnable {} 1.继承Thread 通过继承 Thread 并...

FritzXu的博客 204

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

多线程学习笔记

线程(Thread)是一个程序内部的一条执行流程。程序中如果只有一条执行流程,那这个程序就是单线程的程序。多线程是指从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行)。当多个线程共享操作共享的资源时,线程间通过某种方式告知自己的状态,以相互协调,避免无效的资源争夺

qq_62926721的博客 652

Java学习笔记-多线程

线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。进程是程序的基本执行实体。当核心线程都在运行以及任务队列满之后才能运行临时线程。都满了之后,舍弃任务10/** 参数一:核心线程数量 >=0* 参数二:最大线程数 >0 最大数量 >= 核心线程数量* 参数三:空闲线程最大存活时间 >=0* 参数四:时间单位 用TimeUnit指定* 参数五:任务队列 不能为null* 参数六:创建线程工厂 不能为null* 参数七:任务的拒绝策略 不能为null。

qq_45773654的博客 884

【JAVA】多线程学习笔记

多任务:多条执行路径,主线程和子线程并行交替执行程序:指令和数据的有序集合进程(Process):执行程序的一次执行过程,是资源分配的单位,每个进程有独立的地址空间线程(Thread):是CPU调度和执行的单位,一个进程可以有多个线程,共享进程的地址空间。线程有优先权,新线程的优先级较高。*真正的多线程是指多核/多CPU,如服务器。只有一个CPU的情况下,在同一个时间点只能执行一个代码,但是因为切换的很快所以就有同时执行的错觉。

weixin_47979757的博客 860

Java6学习笔记56——多线程编程——线程的创建方法2

本文已搬家至【Java学习笔记】多线程

gnuhpc的专栏 1052

多线程学习笔记01线程、进程、多线程与线程创建

多线程学习笔记01线程、进程、多线程与线程创建。

2501_90425057的博客 1667

模拟三个老师同时分发80份学习笔记,每个老师相当于一个线程。

模拟三个老师同时分发80份学习笔记,每个老师相当于一个线程。 class Teacher implements Runnable { private int notes = 80; public void run() { while (true) { dispatchNotes(); // 调用售票方法 if ...

烟敛寒林的博客 1万+

Python线程与进程——学习笔记

未完待续

薛倩的专栏 575

学习笔记二:java多线程

学习笔记二:java多线程

ningjia2013的专栏 412

05.Python中的线程学习笔记

这里要说明的是主线程在每一次遇见 t = threading.Thread(target=work)的时候都会创建一个子线程,可以理解为细胞分裂,当然了子线程也可以创建子线程,只是在这里我们的代码没有体现而已啦。出现死锁的三种情况:1.同步锁上锁两次2.锁对象没有释放3.在全局中创建了两个锁对象,并且任务一上锁锁对象1,释放锁对象2,任务二上锁锁对象2,释放锁对象1。线程都会获取到各自的第一个锁,但在尝试获取第二个锁时,由于锁已经被对方线程获取,所以会被阻塞,从而使得两个线程都无法进行下去,造成了死锁。

2302_81214177的博客 851
上一篇: RTP打包发送H264之封包详解
下一篇: 堆--学习笔记
BERLIN23
博客等级 码龄14年 7粉丝 16原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值